算法——排序之简单排序

排序是将一组对象按照某个逻辑顺序重新排列的过程。现在计算机的广泛使用使得数据无处不在,整理数据就变得非常重要了。而整理数据的第一步往往都是进行排序。


简单排序

冒泡排序

原理:

将所有临近的两个的对象按照某个逻辑一一进行比较,通常是由大到小或小到大,比较之后交换两个对象的位置。

反复操作,最终即可排序成功。


如上图所示,我们发现,进行一次循环,冒泡排序只能确定一个对象的位置。第一次循环只能够确定一个对象的位置。所以我们需要多少次循环呢?我们就可以的得出,我们总共需要n-1次循环。因为n-1次循环能确定n-1个对象的位置,这时候自然最后一个对象的位置也确定了。

代码如下:

public static void sort(Comparable[] a) {
	for (int i = 0; i < a.length - 1; i++) {
		for (int j = 0; j < a.length - 1; j++) {
			if (less(a[j + 1], a[j])) {
				swap(a, j, j + 1);
			}
		}
	}
}
实际也就是依靠两层循环,外层控制循环次数,内层逐个进行比较。


冒泡排序的优点就是简单,空间复杂度低。缺点就是慢!无用功多。

时间复杂度O(n^2)


选择排序

原理:

首先找到当前数组中最小的元素,将这个元素和第一个元素进行交换位置。其次,在剩下的数组中找到最小的元素,将它和数组中第二个元素进行位置的交换。依次类推,就可以达到排序的效果。简单来说就是每次都选最小的那个,放在前面。

代码:

public static void sort(Comparable[] a) {
	for (int i = 0; i < a.length - 1; i++) {
		int minIndex = i;
		for (int j = i + 1; j < a.length; j++) {
			if (less(a[j], a[minIndex])) {
				minIndex = j;
			}
		}
		swap(a, minIndex, i);
	}
}

选择排序将数组分成两个部分,已经排序好的和未排序两部分。每次都从未排序部分中找到最小的元素,增加到排好序的末尾。

同样的,外层循环控制次数,内层循环找到最小的元素。外层循环,因为只要找到n-1个元素的位置,第n个元素的位置就直接确定了。所以外层循环需要n-1次。

我个人认为选择排序是最简单理解的排序之一。

时间复杂度O(n^2)


插入排序

插入排序就和平常打扑克牌一样。整理扑克牌的一般使用的就是插入排序。整理方法是一张一张来,将每一张牌插入其他已经有序的牌中的适当位置。当然,在数组操作中,如果需要给某个元素插入,腾出空间,则需要将其他元素向后移动一位。


代码“:

public static void sort(Comparable[] a) {
	for (int i = 1; i < a.length; i++) {
		for (int j = i - 1; j >= 0 && less(a[j+1], a[j]); j--) {
			swap(a, j, j + 1);
		}
	}
}
这个代码简洁,但是却不容易让人看懂。不个人不推荐这么做。而是应该将思路表达的清晰。

改变:

public static void sort(Comparable[] a) {
	for (int i = 1; i < a.length; i++) {
		int position = findPosition(a, i); // 找到应该插入的位置
		Comparable tempI = a[i];
		for (int j = i - 1; j >= position; j--) { // 为插入的位置腾出空间,向后挪动
			a[j + 1] = a[j];
		}
		a[position] = tempI; // 插入值
	}
}

public static int findPosition(Comparable[] a, int i) {
	for (int j = i - 1; j >= 0; j--) {
		if (less(a[j], a[i])) return j + 1;
	}
	return 0;
}

和选择排序不同,插入排序所需的时间和取决于输入元素的初始顺序。对一个已经接近有序的数组进行排序,会比对顺序随机的数组进行排序快得多。

平均时间复杂度:O(n^2)。最好情况时间复杂度O(n)。

插入排序对于部分有序的数组来说非常高效,这是它最大的优点。


希尔排序

希尔排序是对插入排序的一个优化。对于大规模的乱序数组,插入排序会比较缓慢,因为他需要慢慢的向前找到位置,所以元素只能一点一点的从数组中移动到另一端。

希尔排序本质就是分组的插入排序,但是它并不需要一个一个比较,而是能将元素一下子移到一个比较远的地方。

原理:

先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高。


上图中,方框指向的是同一组,也就是将同一组数据排序。当分组越来越少,一组中元素越来越多的时候,排序就基本完成了。而最终,当所有元素都在同一组中的时候,就是简单插入排序。因为这个时候数组已经接近有序了,所以排序起来非常快。


代码:

public static void sort(Comparable[] a) {
	int gap = 1;
	while (gap < a.length /3) gap = 3*gap + 1;
	while (gap >= 1) {
		
		for (int i = 0; i < gap; i++) {
			for (int j = i + gap; j < a.length; j += gap) {
				int position = findPosition(a, i, j, gap);
				Comparable tempJ = a[j];
				for (int k = j - gap; k >= position; k -= gap) {
					a[k + gap] = a[k];
				}
				a[position] = tempJ;
			}
		}
		
		gap /= 3;
	}
}

public static int findPosition(Comparable[] a, int begin, int current, int gap) {
	for (int i = current - gap; i >= begin; i -= gap) {
		if (less(a[i], a[current])) return i + gap;
	}
	return begin;
}
和插入排序非常类似,仅仅是分组的插入排序。


比较:

代码:

public static void main(String[] args) {
  final int NUM = 10000;
  Integer[] a1 = new Integer[NUM];
  Integer[] a2 = new Integer[NUM];
  Integer[] a3 = new Integer[NUM];
  Integer[] a4 = new Integer[NUM];
  for (int i = 0; i < NUM; i++) {
    a1[i] = (int)(Math.random() * NUM);
    a2[i] = a1[i];
    a3[i] = a1[i];
    a4[i] = a1[i];
  }
  
  long startTime;
  long endTime;
  /*
   * 交换
   * */
  startTime = System.currentTimeMillis();   //获取开始时间
  switchSort.sort(a2);
  assert isSorted(a2);
  endTime = System.currentTimeMillis();
  System.out.println("交换排序cost: " + (endTime - startTime) + " ms");
  
  /*
   * 插入
   * */
  startTime = System.currentTimeMillis();   //获取开始时间
  InsertSort.sort(a3);
  assert isSorted(a3);
  endTime = System.currentTimeMillis();
  System.out.println("插入排序cost: " + (endTime - startTime) + " ms");
  
  /*
   * 冒泡
   * */
  startTime = System.currentTimeMillis();   //获取开始时间
  bubbleSort.sort(a1);
  assert isSorted(a1);
  endTime = System.currentTimeMillis();
  System.out.println("冒泡排序cost: " + (endTime - startTime) + " ms");
  
  /*
   * shell
   * */
  startTime = System.currentTimeMillis();   //获取开始时间
  ShellSort.sort(a4);
  assert isSorted(a4);
  endTime = System.currentTimeMillis();
  System.out.println("希尔排序cost: " + (endTime - startTime) + " ms");
}
运行多次,我们会发现:

当数据量不大的时候,插入排序比交换排序快。而数据量大起来,交换排序就比插入排序更加快了。

当然,如果数组是接近有序的,插入排序的性能会非常强大。

冒泡排序比较缓慢。

而希尔排序的性能非常不错。即使数据量非常大,希尔排序的性能也是不错的。希尔排序的平均时间复杂度是O(n^1.3)左右。相对简单插入排序来说确实是极大的提升。并且希尔排序的代码量并不大。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章