`

《Java数据结构和算法》学习笔记(2)——4种简单排序算法

阅读更多
每篇笔记都会附上随书的Applet演示程序,有算法看不明白的,可以下载Applet运行起来(直接打开html文件即可),可以很容易地看清楚算法的每一步。

冒泡排序法

冒泡排序的原理很简单,拿整数数组的升序排序来说:从头到尾循环地比较相邻的两个数字,如果前一个数字比后一个大,则交换它们的位置,然后拿较大的这个数字跟下一个比较;如果前一个数字比后一个小,它们的位置不动,直接拿较大的数字跟下一个比较,这样循环一次完毕,最大的数字被排到了最后。接着开始第二轮循环,再从头到尾循环比较相信的两个数字,循环完毕后,第二大的数字被排到了倒数第二的位置上。长度为N的数组一共需要N次循环(其实最后一次没有必要),最坏的情况下(逆序),第1次循环要交换数字N-1次,第2次循环要交换数字N-2次,最后一次循环要交换0次,总共交换(N-1)*N/2 = (N^2-N)/2次,约N^2/2次(忽略N次不会有很大差别,特别是在N很大的时候)。如果数据是随机的,平均每次排序需要交换数字N^2/4次,也就是说交换次数和N^2/4成正比,由于常数不算在大O表示法中,可以忽略掉这个4,认为冒泡排序运行需要O(N^2)时间级别。
以下是冒泡排序代码:
package dsaa.array;
/**
 * @(#)ArrayUtil.java 2008-12-25 下午08:44:46
 * 
 * @author Qiu Maoyuan
 * Array Util
 */
public class ArrayUtil {

	public static void bubbleSort(int[] array){
		for(int i=0; i<array.length - 1; i++){
			for(int j=0; j<array.length - i - 1; j++){
				if(array[j]>array[j + 1]){
					swap(array, j, j + 1);
				}
			}
		}
	}

	private static void swap(int[] array, int index1, int index2) {
		int temp = array[index1];
		array[index1] = array[index2];
		array[index2] = temp;
	}
}

选择排序法

选择排序法是冒泡排序法的改进,冒泡排序在最坏的情况下每次循环比较的时候都进行交换操作,而选择排序每循环一次只要交换数字1次。原理是:在第一次循环的时候用一个临时变量指向数组第1个位置,假设第1个位置是最小值,依次比较数组右边的其它数字,当遇到比第1个位置的数更小的值时,将临时变量指向这个更小的数的位置,循环过一次,便得到整个数组中最小的值,把它与第1个位置的数字交换,第一次循环完毕。第二次循环从第2个位置开始……一共需要N次循环(同样,最后一次也没有必要)。这样,选择排序法交换数字的次数跟数组长度成正比,用大O表示法表示为O(N)。
下面的代码中加入了选择排序法selectionSort:
package dsaa.array;
/**
 * @(#)ArrayUtil.java 2008-12-25 下午08:44:46
 * 
 * @author Qiu Maoyuan
 * Array Util
 */
public class ArrayUtil {

	public static void bubbleSort(int[] array){
		for(int i=0; i<array.length - 1; i++){
			for(int j=0; j<array.length - i - 1; j++){
				if(array[j]>array[j + 1]){
					swap(array, j, j + 1);
				}
			}
		}
	}
	
	public static void selectionSort(int[] array){
		for(int i=0; i<array.length - 1; i++){
			int minIndex = i;
			for(int j=i; j<array.length - 1; j++){
				if(array[j + 1] < array[minIndex]){
					minIndex = j + 1;
				}
			}
			swap(array, i, minIndex);
		}
	}

	private static void swap(int[] array, int index1, int index2) {
		int temp = array[index1];
		array[index1] = array[index2];
		array[index2] = temp;
	}
}

插入排序法

插入排序法保持数组的前面一部分有序,并不断地把后面无序的数据插入到前面有序的一部分,同时仍然保持前面的数据有序。这里仍然用整数数组的升序排序来说明其原理:首先假设数组第1个数据为有序数组(虽然只有1个数据,但那肯定是有序的数组),然后将第2个数据与前面1个数据对比,如果第2个数据比前一个小,就先把第2个数据保存到临时变量中,然后依次往前循环,与前面的每个数据(第一次循环只有1个)作比较,如果前面的数据比临时变量大,就把前面的数据往后移1个位置(复制到后一个位置上),直到遇到比临时变量小的数据,把临时变量插入到比它小的那个数据的后面。如果对比到了下标为0的数据之后仍然没有比临时变量小的数据,就直接把临时变量插入到下标为0的位置上。用插入排序法进行逆序排序的情况下,第1次循环需要3次复制(复制出临时变量,把有序部分后移的时候也要复制,再把临时变量复制到正确的位置),第2次循环需要4次复制……第N-1次循环(因为是从第2个数据开始,所以只有N-1次)需要N+1次复制,一共(N^2+3N+3)/2次复制。如果数组中的数据是随机的,则平均需要(N^2+3N+3)/4次复制。同样,用大O表示法的时候,把常数3与3/4去掉(可以看作(N^2+3N)/4 + 3/4),再忽略掉3N,可以认为插入排序需要O(N^2)级别时间。但是一次复制与一次交换消耗的时间不同(一次交换有3次复制),所以插入排序法比冒泡排序法快得多,比选择排序略快。
以下代码中添加了插入排序法insertionSort:
package dsaa.array;
/**
 * @(#)ArrayUtil.java 2008-12-25 下午08:44:46
 * 
 * @author Qiu Maoyuan
 * Array Util
 */
public class ArrayUtil {

	public static void bubbleSort(int[] array){
		for(int i=0; i<array.length - 1; i++){
			for(int j=0; j<array.length - i - 1; j++){
				if(array[j]>array[j + 1]){
					swap(array, j, j + 1);
				}
			}
		}
	}
	
	public static void selectionSort(int[] array){
		for(int i=0; i<array.length - 1; i++){
			int minIndex = i;
			for(int j=i; j<array.length - 1; j++){
				if(array[j + 1] < array[minIndex]){
					minIndex = j + 1;
				}
			}
			swap(array, i, minIndex);
		}
	}
	
	public static void insertionSort(int[] array){
		
		for(int i=1; i<array.length; i++){
			if(array[i]<array[i - 1]){
				int temp = array[i];
				int j = i - 1;
				while(j>=0 && temp<array[j]){
					array[j + 1] = array[j];
					j--;
				}
				array[j + 1] = temp;
			}
		}
	}

	private static void swap(int[] array, int index1, int index2) {
		int temp = array[index1];
		array[index1] = array[index2];
		array[index2] = temp;
	}
}

奇偶排序法

奇偶排序法的思路是在数组中重复两趟扫描。第一趟扫描选择所有的数据项对,a[j]和a[j+1],j是奇数(j=1, 3, 5……)。如果它们的关键字的值次序颠倒,就交换它们。第二趟扫描对所有的偶数数据项进行同样的操作(j=2, 4, 6……)。重复进行这样两趟的排序直到数组全部有序。
下面的代码是我原来的写法:
	public void oddEvenSort(int[] array){ 
		for (int i = 0;	i < array.length; i += 2){ 
			int j = 0;
			scan(array, j);	
			j = 1;
			scan(array, j);	
		} 
	}

	private	void scan(int[]	array, int j) {
		while (j < array.length	- 1){ 
			if (array[j] > array[j + 1]){ 
				swap(array, j, j + 1);
			} 
			j += 2;
		}
	} 

	private	static void swap(int[] array, int index1, int index2) {
		int temp = array[index1];
		array[index1] =	array[index2];
		array[index2] =	temp;
	}

后来有朋友提出建议,我小小的改动了一下,对随机数组排序的效率略有提高:
	public static void oddEvenSort(int[] array) {
		boolean unsorted = true;
		while (unsorted) {
			unsorted = false;
			int i = 1;
			boolean oddUnsorted = scan(array, i);
			i = 0;
			boolean evenUnsorted = scan(array, i);
			unsorted = oddUnsorted || evenUnsorted;
		}
	}

	private static boolean scan(int[] array, int i) {
		boolean unsorted = false;
		while (i < array.length - 1) {
			if (array[i] > array[i + 1]) {
				swap(array, i, i + 1);
				unsorted = true;
			}
			i += 2;
		}
		return unsorted;
	}

	private static void swap(int[] array, int index1, int index2) {
		int temp = array[index1];
		array[index1] = array[index2];
		array[index2] = temp;
	}

和冒泡排序法一样,奇偶排序的时间复杂度为O(N^2)。
《Java数据结构和算法》 写道
奇偶排序实际上在多处理器环境中很有用,处理器可以分别同时处理每一个奇数对,然后又同时处理偶数对。因为奇数对是彼此独立的,每一刻都可以用不同的处理器比较和交换。这样可以非常快速地排序。

以上四种算法的运行效率我在本机上测试了一下,结果如下:

冒泡排序法对长度为1万的数组进行逆序排序,平均花费时间0.235秒;对随机数组进行升序排序,平均花费时间0.329秒。。。。这个这个。。。

选择排序法对长度为1万的数组进行逆序排序,平均花费时间0.188秒;对随机数组进行升序排序,平均花费时间0.218秒…………

插入排序法对长度为1万的数组进行逆序排序,平均花费时间0.141秒;对随机数组进行升序排序,平均花费时间0.078秒。(这回才正常,相当奇怪呀……我写的算法应该没有错吧,为什么冒泡排序和选择排序在逆序排序的时候比较快呢?? 具体原因暂时先参考问答频道这里http://www.iteye.com/problems/9236

奇偶排序法对长度为1万的数组进行逆序排序,平均花费时间0.203秒;对随机数组进行升序排序,平均花费时间0.266秒。。。

===================2008-12-29===================

昨天复习了链表一章,增加了一个表插入排序法,同时用泛型改写了一下以前的各排序算法。测试结果是:
引用
冒泡-随机:1.265
冒泡-逆序:1.422
选择-随机:0.969
选择-逆序:0.86
插入-随机:0.735
插入-逆序:1.375
表插入-随机:0.484
表插入-逆序:0.641

附上时间测试代码:
import java.util.Random;
public class ArrayUtil {	

	/**
	 * 冒泡排序
	 * @param array
	 */
	public static <T extends Comparable<? super T>> void bubbleSort(T[] array){
		for(int i=0; i<array.length - 1; i++){
			for(int j=0; j<array.length - i - 1; j++){
				if(array[j].compareTo(array[j + 1])>0){
					swap(array, j, j + 1);
				}
			}
		}
	}
	
	/**
	 * 选择排序
	 * @param array
	 */
	public static <T extends Comparable<? super T>> void selectionSort(T[] array){
		for(int i=0; i<array.length - 1; i++){
			int minIndex = i;
			for(int j=i; j<array.length - 1; j++){
				if(array[j + 1].compareTo(array[minIndex])<0){
					minIndex = j + 1;
				}
			}
			swap(array, i, minIndex);
		}
	}
	
	/**
	 * 插入排序
	 * @param array
	 */
	public static <T extends Comparable<? super T>> void insertionSort(T[] array){
		for(int i=1; i<array.length; i++){
			if(array[i].compareTo(array[i - 1])<0){
				T temp = array[i];
				int j = i - 1;
				while(j>=0 && temp.compareTo(array[j])<0){
					array[j + 1] = array[j];
					j--;
				}
				array[j + 1] = temp;
			}
		}
	}
	
	/**
	 * 奇偶排序
	 * @param array
	 */
	public static <T extends Comparable<? super T>> void oddEvenSort(T[] array) {
		boolean unsorted = true;
		while (unsorted) {
			unsorted = false;
			int i = 1;
			boolean oddUnsorted = scan(array, i);
			i = 0;
			boolean evenUnsorted = scan(array, i);
			unsorted = oddUnsorted || evenUnsorted;
		}
	}

	private static <T extends Comparable<? super T>> boolean scan(T[] array, int i) {
		boolean unsorted = false;
		while (i < array.length - 1) {
			if (array[i].compareTo(array[i + 1])>0) {
				swap(array, i, i + 1);
				unsorted = true;
			}
			i += 2;
		}
		return unsorted;
	}

	private static <T extends Comparable<? super T>> void swap
		(T[] array, int index1, int index2) {
		T temp = array[index1];
		array[index1] = array[index2];
		array[index2] = temp;
	}
	
	/**
	 * 表插入排序法
	 * @param <T>
	 * @param array
	 */
	@SuppressWarnings("unchecked")
	public static <T extends Comparable<? super T>> void listInsertionSort(T[] array){
		SortedLinkList<?> linkList = new SortedLinkList(array);
		T[] newArray = (T[])new Comparable[array.length];
		for(int i=0; i<newArray.length; i++){
			newArray[i] = (T)linkList.remove();
		}
		array = newArray;
	}

	public static void main(String[] args){
		Integer[] array = new Integer[10000];
		generateRandomArray(array);
		long b = System.currentTimeMillis();
		ArrayUtil.bubbleSort(array);
		long e = System.currentTimeMillis();
		System.out.println("冒泡-随机:" + (e - b)/1000.0);
		
		generateContradictoryArray(array);
		b = System.currentTimeMillis();
		ArrayUtil.bubbleSort(array);
		e = System.currentTimeMillis();
		System.out.println("冒泡-逆序:" + (e - b)/1000.0);

		generateRandomArray(array);
		b = System.currentTimeMillis();
		ArrayUtil.selectionSort(array);
		e = System.currentTimeMillis();
		System.out.println("选择-随机:" + (e - b)/1000.0);
		
		generateContradictoryArray(array);
		b = System.currentTimeMillis();
		ArrayUtil.selectionSort(array);
		e = System.currentTimeMillis();
		System.out.println("选择-逆序:" + (e - b)/1000.0);

		generateRandomArray(array);
		b = System.currentTimeMillis();
		ArrayUtil.insertionSort(array);
		e = System.currentTimeMillis();
		System.out.println("插入-随机:" + (e - b)/1000.0);
		
		generateContradictoryArray(array);
		b = System.currentTimeMillis();
		ArrayUtil.insertionSort(array);
		e = System.currentTimeMillis();
		System.out.println("插入-逆序:" + (e - b)/1000.0);

		generateRandomArray(array);
		b = System.currentTimeMillis();
		ArrayUtil.listInsertionSort(array);
		e = System.currentTimeMillis();
		System.out.println("表插入-随机:" + (e - b)/1000.0);
		
		generateContradictoryArray(array);
		b = System.currentTimeMillis();
		ArrayUtil.listInsertionSort(array);
		e = System.currentTimeMillis();
		System.out.println("表插入-逆序:" + (e - b)/1000.0);
	}

	private static void generateContradictoryArray(Integer[] array) {
		for(int i=0; i<array.length; i++){
			array[i] = array.length - i;
		}
	}

	private static void generateRandomArray(Integer[] array) {
		Random random = new Random();
		for(int i=0; i<array.length; ){
			int item = random.nextInt(10000);
			int j;
			if(i==0) array[i]=item;
			for(j=0; j<i; ){
				if(array[j]==item) break;
				j++;
			}
			if(j==i){
				array[i] = item;
				i++;
			}
		}
	}
}

class SortedLinkList<E extends Comparable<E>> {

	private Link first;

	public SortedLinkList() {
	}

	public SortedLinkList(E[] array) {
		for (E element : array) {
			add(element);
		}
	}

	public void add(E value) {
		Link newElement = new Link(value);
		if (isEmpty()) {
			first = newElement;
		} else {
			Link current = first;
			Link previous = null;
			while (!endOfLink(current) && current.element.compareTo(value) >= 0) {
				previous = current;
				current = current.next;
			}
			if (!endOfLink(current)) {
				newElement.next = current;
			}
			if (current == first)
				first = newElement;
			else
				previous.next = newElement;
		}
	}

	public E getFirst() {
		if (isEmpty())
			throw new IllegalStateException("Linklist is empty");
		return first.element;
	}

	public boolean isEmpty() {
		return first == null;
	}

	public E remove() {
		E value = getFirst();
		first = first.next;
		return value;
	}

	public boolean delete(E element) {
		if (isEmpty())
			return false;
		Link current = first;
		Link previous = first;
		while (!element.equals(current.element)) {
			if (endOfLink(current.next)) {
				return false;
			} else {
				previous = current;
				current = current.next;
			}
		}
		if (current == first)
			first = first.next;
		else
			previous.next = current.next;
		return true;
	}

	private boolean endOfLink(Link link) {
		return link == null;
	}

	@Override
	public String toString() {
		StringBuilder buffer = new StringBuilder();
		Link current = first;
		while (current != null) {
			buffer.append(current.toString());
			current = current.next;
		}
		return buffer.toString();
	}

	private class Link {

		private E element;

		private Link next;

		public Link(E element) {
			this.element = element;
		}

		@Override
		public String toString() {
			return "[" + element.toString() + "]";
		}
	}
}

分享到:
评论

相关推荐

    《Java数据结构和算法》学习笔记(5)——递归 归并排序

    在本篇《Java数据结构和算法》学习笔记中,我们将深入探讨递归和归并排序。递归是一种强大的编程技术,常用于解决复杂问题,而归并排序则是利用递归实现的一种高效排序算法。 首先,让我们理解什么是递归。递归是...

    《Java数据结构和算法》学习笔记(1)——数组 二分法 大O表示法

    本文将深入探讨《Java数据结构和算法》学习笔记的第一部分,主要聚焦于数组、二分法以及大O表示法。这些基础知识对于提升代码性能和优化解决方案具有决定性的作用。 **数组**是编程中最基本的数据结构之一,它在...

    《Java数据结构和算法》学习笔记(3)——栈和队列

    本文将基于《Java数据结构和算法》学习笔记中的“栈和队列”部分进行深入探讨。 栈(Stack)是一种线性数据结构,遵循后进先出(LIFO, Last In First Out)的原则。在栈中,元素的添加(压栈)和移除(弹栈)都是在...

    Java数据结构和算法-学习笔记

    ### Java数据结构与算法——学习笔记 #### 一、引言 在计算机科学领域,**数据结构**与**算法**是两个极其重要的概念。数据结构指的是数据的组织方式,而算法则是解决特定问题的一系列步骤。这两者是编程的基础,...

    数据结构与问题求解——java语言描述 源码

    本资料集是基于Java语言的实现,由著名计算机科学家Mark Allen Weiss所著的《数据结构与问题求解——java语言描述》(第三版)的源码。该书通过丰富的实例和深入的理论讲解,帮助读者理解和掌握各种经典的数据结构...

    leetcode算法学习笔记,Python,Golang,Java.zip

    《LeetCode算法学习笔记——Python、Golang与Java篇》 在编程领域,LeetCode是一个备受推崇的在线平台,它提供了丰富的算法题目,旨在帮助开发者提升编程技能,特别是解决算法问题的能力。本压缩包文件“leetcode...

    GitHub一战封神,字节跳动内部顶级数据结构刷题学习笔记根本停不下来(csdn)————程序.pdf

    标题和描述中提到的是一份源自字节跳动内部的数据结构刷题学习笔记,这份笔记在GitHub上引起广泛关注,被认为是一份有助于提升编程能力,尤其是对于准备字节跳动面试的程序员极具价值的资源。主要关注点在于各种排序...

    哈尔滨工业大学《数据结构与算法》、《软件开发实践》作业及实验的Scheme解法。.zip

    例如,排序算法(如冒泡排序、快速排序、归并排序)和搜索算法(如线性搜索、二分搜索)都依赖于数据结构的选择。 C/C++/JAVA/Python是四种广泛使用的编程语言,各有其特点和适用场景。C和C++以其高效性和低级特性...

    福建省算法夏令营2018——课件

    2. **数据结构详解**:数据结构是算法的基础,课件可能涉及数组、链表、栈、队列、树(二叉树、平衡树如AVL和红黑树)、图、哈希表等。理解它们的性质和操作,对于优化算法至关重要。 3. **算法实例与应用**:通过...

    《数据结构——C++实现》(第二版)课本源代码.zip

    通过《数据结构——C++实现》(第二版)的学习,读者不仅能掌握各种数据结构的原理,还能熟悉C++编程,为后续的软件开发和算法设计打下坚实基础。同时,书中提供的源代码是宝贵的实践资源,可以帮助读者在实践中学习...

    达内JAVA培训综合笔记

    同时,也介绍了两种常见的排序算法——插入排序和冒泡排序,以及二分法查找。这部分最后提及了Java系统API方法的调用、二进制基础和Java基础其他注意事项。 三、面向对象 面向对象是Java编程的核心思想,笔记中对类...

    [转] 大量算法下载地址

    - **内容介绍**:这是一份针对数据结构与算法分析的学习笔记,旨在帮助读者更好地理解并掌握这两方面的核心知识。 - **适用对象**:适合正在学习计算机科学及相关专业的学生。 - **学习重点**:涵盖了数据结构的基本...

    java 学习笔记

    ### Java学习笔记精要 #### 一、面向对象的基础与集合框架 面向对象是Java的核心概念,它将现实世界中的实体抽象为类和对象,强调数据封装、继承和多态性。集合框架则是用于存储和操作集合数据的重要工具,包括`...

    大一下Java大作业——双人联机小游戏森林冰火人.zip

    5. **数据结构与算法**:游戏中的物体位置、碰撞检测等都需要高效的数据结构(如数组、链表、队列、栈等)和算法(如搜索、排序、图遍历等)来处理。 6. **游戏逻辑**:实现游戏规则,如角色移动、碰撞检测、得分...

    21天学通java

    - 该书籍专注于使用Java实现常见的数据结构(如链表、树、图等)和算法(排序、查找等),对于提高编程能力非常有帮助。 - 对于希望在Java中实现高效数据处理的学习者来说,这本书是不可或缺的。 ### 高级主题 1...

    java(18天)笔记,txt文件

    2. **Day03 - 数据类型与变量**: 深入理解Java的两种数据类型——基本类型和引用类型,学习声明、初始化和使用变量,以及不同数据类型的范围和用途。 3. **Day05 - 流程控制**: 学习条件语句(if, if-else, switch...

    javalruleetcode-JavaNote:Java学习笔记

    【Java学习笔记——深入理解与实战应用】 "javalruleetcode-JavaNote"是一个专注于Java学习的资源集合,其中包含了作者在学习Java过程中的笔记、算法解析以及LeetCode的解题经验。这个项目旨在帮助Java初学者和进阶...

    Leetcode、剑指Offer——名企面试官精讲典型编程题.zip

    这本书系统地介绍了软件工程师面试中常遇到的算法和数据结构问题,涵盖了数组、链表、栈、队列、树等基础数据结构以及排序、查找、递归等基本算法。书中每个问题都配有详细的解题思路和代码实现,对于理解和应用这些...

Global site tag (gtag.js) - Google Analytics