常用排序算法之比较排序篇(编辑中)

排序介绍


我们通常所说的排序算法往往指的是内部排序算法,即数据记录在内存中进行排序。

排序算法大体可分为两种:

一种是比较排序,时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn) ~ O ( n 2 ) O(n^2) O(n2) ,主要有:冒泡排序,选择排序,插入排序,归并排序,堆排序,快速排序等。

另一种是非比较排序,时间复杂度可以达到 O ( n ) O(n) O(n) ,主要有:计数排序,基数排序,桶排序等。

这里我们来探讨一下常用的比较排序算法,非比较排序算法将在下一篇文章:常用排序算法之非比较排序中介绍。

常见比较排序算法的性能:


数据结构稳定性平均时间复杂度最坏时间复杂度最优时间复杂度空间复杂度最佳解
冒泡排序数组 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n)总共 O ( n ) O(n) O(n),需要辅助空间 O ( 1 ) O(1) O(1)NO
简单选择排序数组、链表× O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2)总共 O ( n ) O(n) O(n),需要辅助空间 O ( 1 ) O(1) O(1)NO
直接插入排序数组、链表 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n)总共 O ( n ) O(n) O(n),需要辅助空间 O ( 1 ) O(1) O(1)NO
希尔排序数组、链表× O ( n l o g n ) O(nlogn) O(nlogn)~ O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n)总共 O ( n ) O(n) O(n),需要辅助空间 O ( 1 ) O(1) O(1)NO
堆排序数组、链表× O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn)总共 O ( n ) O(n) O(n),需要辅助空间 O ( 1 ) O(1) O(1)NO
归并排序数组、链表 O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn)总共 O ( n ) O(n) O(n),需要辅助空间 O ( n ) O(n) O(n)NO
快速排序数组、链表× O ( n l o g n ) O(nlogn) O(nlogn) O ( n 2 ) O(n^2) O(n2) O ( n l o g n ) O(nlogn) O(nlogn)总共 O ( n ) O(n) O(n),需要辅助空间 O ( l o g n ) O(logn) O(logn)~ O ( n ) O(n) O(n)NO

排序算法的稳定性

排序算法的稳定性是我们很容易忽略的(腾讯校招2016笔试题曾考过)。

排序算法稳定性的简单形式化定义为:如果 Ai = Aj ,排序前 Ai 在 Aj 之前,排序后 Ai 还在 Aj 之前,则称这种排序算法是稳定的。 通俗地讲就是保证排序前后两个相等的数的相对顺序不变。

对于不稳定的排序算法,只要举出一个实例,即可说明它的不稳定性;而对于稳定的排序算法,必须对算法进行分析从而得到稳定的特性。需要注意的是,排序算法是否为稳定的是由具体算法决定的,不稳定的算法在某种条件下可以变为稳定的算法,而稳定的算法在某种条件下也可以变为不稳定的算法。

例如,对于冒泡排序,原本是稳定的排序算法,如果将记录交换的条件改成A[i] >= A[i + 1],则两个相等的记录就会交换位置,从而变成不稳定的排序算法。

其次,说一下排序算法稳定性的好处排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,前一个键排序的结果可以为后一个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位排序后元素的顺序在高位也相同时是不会改变的。

冒泡排序(Bubble Sort)


介绍

冒泡排序(英语:Bubble Sort)又称为泡式排序,是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

冒泡排序对 n n n 个项目需要 O( n 2 n^2 n2) 的比较次数,且可以原地排序。尽管这个算法是最简单了解和实现的排序算法之一,但它对于包含大量的元素的数列排序是很没有效率的。

冒泡排序是与插入排序拥有相等的运行时间,但是两种算法在需要的交换次数却很大地不同。在最坏的情况,冒泡排序需要 O ( n 2 ) O(n^2) O(n2) 次交换,而插入排序只要最多 O ( n ) O(n) O(n) 交换。冒泡排序的实现(类似下面)通常会对已经排序好的数列拙劣地运行( O ( n 2 ) O(n^2) O(n2) ),而插入排序在这个例子只需要 O ( n ) O(n) O(n) 个运算。因此很多现代的算法教科书避免使用冒泡排序,而用插入排序取代之。冒泡排序如果能在内部循环第一次运行时,使用一个旗标来表示有无需要交换的可能,也可以把最优情况下的复杂度降低到 O ( n ) O(n) O(n) 。在这个情况,已经排序好的数列就无交换的需要。若在每次走访数列时,把走访顺序反过来,也可以稍微地改进效率。有时候称为鸡尾酒排序,因为算法会从数列的一端到另一端之间穿梭往返。

冒泡排序算法的运作如下

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

由于它的简洁,冒泡排序通常被用来对于程序设计入门的学生介绍算法的概念。
使用冒泡排序为一列数字进行排序的过程,图1

使用冒泡排序为一列数字进行排序的过程,图1

在这里插入图片描述

使用冒泡排序为一列数字进行排序的过程,图2


伪代码

function bubble_sort (array, length) {
    var i, j;
    for(i from 0 to length-1){
        for(j from 0 to length-1-i){
            if (array[j] > array[j+1])
                swap(array[j], array[j+1])
        }
    }
}
函数 冒泡排序 输入 一个数组名称为array 其长度为length 
    i 从 1 到 (length - 1) 
        j 从 0 到 (length - 1 - i) 
            如果 array[j] > array[j + 1] 
                交换 array[j] 和 array[j + 1] 的值 
            如果结束 
        j循环结束 
    i循环结束 
函数结束

代码实现

Java

private int[] bubbleSort(int[] array) {
    int temp;
    // 每次最大元素就像气泡一样"浮"到数组的最后
    for (int i = 0; i < array.length-1; i++) {
    	// 依次比较相邻的两个元素,使较大的那个向后移
        for (int j = 0; j < array.length - 1 - i; j++) {
        	// 如果条件改成A[i] >= A[i + 1],则变为不稳定的排序算法
            if (array[j] > array[j + 1]) {
                temp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = temp;
            }
        }
    }
    return array;
}

尽管冒泡排序是最容易了解和实现的排序算法之一,但它对于少数元素之外的数列排序是很没有效率的

选择排序(Selection Sort)


介绍

选择排序也是一种简单直观的排序算法。它的工作原理很容易理解:初始时在序列中找到最小(大)元素,放到序列的起始位置作为已排序序列;然后,再从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾。以此类推,直到所有元素均排序完毕

注意选择排序与冒泡排序的区别:冒泡排序通过依次交换相邻两个顺序不合法的元素位置,从而将当前最小(大)元素放到合适的位置;而选择排序每遍历一次都记住了当前最小(大)元素的位置,最后仅需一次交换操作即可将其放到合适的位置。
在这里插入图片描述

使用选择排序为一列数字进行排序的过程,图1
在这里插入图片描述
使用选择排序为一列数字进行排序的过程,图2


伪代码

SELECTION-SORT(A)								执行次数
1	for j = 1 to Length(A)						n
2		i = j									n
3		key = A(i)								n  
4		for i to Lenth(A)						n(n+1)/2
5			if key > A(i)						...
6				key = A(i)						...
7				k = i							...
8		A(k) = A(j)								...
9		A(j) = key								...

代码实现

Java

    public static void selectionSort(Comparable[] a) {
        int N = a.length;
        for (int i = 0; i < N - 1; i++) {
            int min = i;
            for (int j = i + 1; j < N; j++) {
                if (less(a[j], a[min])) min = j;
            }
            exch(a, i, min);
        }
    }

    private static boolean less(Comparable v, Comparable w) {
        return v.compareTo(w) < 0;
    }

    private static void exch(Comparable[] a, int i, int j) {
        Comparable t = a[i];
        a[i] = a[j];
        a[j] = t;
    }

注意

选择排序是不稳定的排序算法,不稳定发生在最小元素与A[i]交换的时刻。

比如序列:{ 5, 8, 5, 2, 9 },一次选择的最小元素是2,然后把2和第一个5进行交换,从而改变了两个元素5的相对次序。

插入排序(Insertion Sort)


介绍

插入排序(英语:Insertion Sort)是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到 O ( 1 ) O(1) O(1) 的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:

  1. 从第一个元素开始,该元素可以认为已经被排序
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  5. 将新元素插入到该位置后
  6. 重复步骤2~5

可以采用二分查找法来减少“比较操作”的数目,而由于“交换操作”的数目不变,算法的时间复杂度依旧为 O ( n 2 ) O(n^2) O(n2) 。该算法可以认为是插入排序的一个变种,称为二分查找插入排序。
使用插入排序为一列数字进行排序的过程,图1

使用插入排序为一列数字进行排序的过程,图1
使用插入排序为一列数字进行排序的过程,图2
使用插入排序为一列数字进行排序的过程,图2


伪代码

INSERTION-SORT(A)
1   for j=2 to A.length
2       key = A[j]
3       //Insert A[j] into the sorted sequence A[1..j-1]
4       i = j - 1
5       while i > 0 and A[i] > key
6           A[i+1] = A[i]
7           i = i - 1
8       A[i+1]=key

代码实现

Java

public void insertionSort(int[] array) {
	for (int i = 1; i < array.length; i++) {
		int key = array[i];
		int j = i - 1;
		while (j >= 0 && array[j] > key) {
			array[j + 1] = array[j];
			j--;
		}
		array[j + 1] = key;
	}
}

插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,比如量级小于千,那么插入排序还是一个不错的选择。 插入排序在工业级库中也有着广泛的应用,在 STL 的 sort 算法和 stdlib 的 qsort 算法中,都将插入排序作为快速排序的补充,用于少量元素的排序(通常为8个或以下)。

桶排序


介绍

桶排序(Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶里。每个桶再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。

桶排序是鸽巢排序的一种归纳结果。

当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间 Θ ( n ) Θ(n) Θ(n) 。但桶排序并不是比较排序,他不受到 O ( n l o g n ) O(nlogn) O(nlogn) 下限的影响。

桶排序以下列程序进行:

  1. 设置一个定量的数组当作空桶子。
  2. 寻访序列,并且把项目一个一个放到对应的桶子去。
  3. 对每个不是空的桶子进行排序。
  4. 从不是空的桶子里把项目再放回原来的序列中。

元素分配到桶中

元素分配到桶中

对桶中元素排序

对桶中元素排序

伪代码

function bucket-sort(array, n) is
  buckets ← new array of n empty lists
  for i = 0 to (length(array)-1) do
    insert array[i] into buckets[msbits(array[i], k)]
  for i = 0 to n - 1 do
    next-sort(buckets[i])
  return the concatenation of buckets[0], ..., buckets[n-1]

代码实现

Java

	private int indexFor(int a, int min, int step) {
		return (a - min) / step;
	}

	public void bucketSort(int[] arr) {

		int max = arr[0], min = arr[0];
		for (int a : arr) {
			if (max < a)
				max = a;
			if (min > a)
				min = a;
		}
		// 该值也可根据实际情况选择
		int bucketNum = max / 10 - min / 10 + 1;
		List buckList = new ArrayList<List<Integer>>();
		// create bucket
		for (int i = 1; i <= bucketNum; i++) {
			buckList.add(new ArrayList<Integer>());
		}
		// push into the bucket
		for (int i = 0; i < arr.length; i++) {
			int index = indexFor(arr[i], min, 10);
			((ArrayList<Integer>) buckList.get(index)).add(arr[i]);
		}
		ArrayList<Integer> bucket = null;
		int index = 0;
		for (int i = 0; i < bucketNum; i++) {
			bucket = (ArrayList<Integer>) buckList.get(i);
			insertSort(bucket);
			for (int k : bucket) {
				arr[index++] = k;
			}
		}

	}

	// 把桶內元素插入排序
	private void insertSort(List<Integer> bucket) {
		for (int i = 1; i < bucket.size(); i++) {
			int temp = bucket.get(i);
			int j = i - 1;
			for (; j >= 0 && bucket.get(j) > temp; j--) {
				bucket.set(j + 1, bucket.get(j));
			}
			bucket.set(j + 1, temp);
		}
	}

希尔排序(Shell Sort)


介绍

希尔排序,也叫递减增量排序,是插入排序的一种更高效的改进版本。希尔排序是不稳定的排序算法。

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  • 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。
  • 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。

希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。
假设有一个很小的数据在一个已按升序排好序的数组的末端。如果用复杂度为 O ( n 2 ) O(n^2) O(n2) 的排序(冒泡排序或直接插入排序),可能会进行 n n n 次的比较和交换才能将该数据移至正确位置。而希尔排序会用较大的步长移动数据,所以小数据只需进行少数比较和交换即可到正确位置。
使用希尔排序为一列数字进行排序的过程

使用希尔排序为一列数字进行排序的过程


伪代码

input: an array a of length n with array elements numbered 0 to n − 1
inc ← round(n / 2)
while inc > 0 do:    
    for i = inc .. n − 1 do:        
        temp ← a[i]        
        j ← i        
        while j ≥  inc and a[j − inc] > temp do:            
            a[j] ← a[j − inc]            
            j ← j − inc        
        a[j] ← temp    
    inc ← round(inc / 2)

代码实现

Java

public static void shellSort(int[] arr) {
    int length = arr.length;
    int temp;
    for (int step = length / 2; step >= 1; step /= 2) {
        for (int i = step; i < length; i++) {
            temp = arr[i];
            int j = i - step;
            while (j >= 0 && arr[j] > temp) {
                arr[j + step] = arr[j];
                j -= step;
            }
            arr[j + step] = temp;
        }
    }
}

希尔排序是不稳定的排序算法,虽然一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱。

比如序列:{ 3, 5, 10, 8, 7, 2, 8, 1, 20, 6 },step = 2 时分成两个子序列 { 3, 10, 7, 8, 20 } 和 { 5, 8, 2, 1, 6 } ,未排序之前第二个子序列中的 8 在前面,现在对两个子序列进行插入排序,得到 { 3, 7, 8, 10, 20 } 和 { 1, 2, 5, 6, 8 } ,即 { 3, 1, 7, 2, 8, 5, 10, 6, 20, 8 } ,两个 8 的相对次序发生了改变。

归并排序(Merge Sort)


介绍

归并排序是创建在归并操作上的一种有效的排序算法,效率为 O ( n l o g n ) O(nlogn) O(nlogn) ,1945年由冯·诺伊曼首次提出。

归并排序的实现分为递归实现非递归(迭代) 实现。递归实现的归并排序是算法设计中分治策略的典型应用,我们将一个大问题分割成小问题分别解决,然后用所有小问题的答案来解决整个大问题。非递归(迭代)实现的归并排序首先进行是两两归并,然后四四归并,然后是八八归并,一直下去直到归并了整个数组。

归并排序算法主要依赖归并(Merge)操作。归并操作指的是将两个已经排序的序列合并成一个序列的操作,归并操作步骤如下:

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列。
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置。
  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置。
  4. 重复步骤3直到某一指针到达序列尾。
  5. 将另一序列剩下的所有元素直接复制到合并序列尾。

在这里插入图片描述

使用归并排序为一列数字进行排序的过程,图1
在这里插入图片描述
使用归并排序为一列数字进行排序的过程,图2

归并排序除了可以对数组进行排序,还可以高效的求出数组小和(即单调和)以及数组中的逆序对。


代码实现

Java递归

static void merge_sort_recursive(int[] arr, int[] result, int start, int end) {
	if (start >= end)
		return;
	int len = end - start, mid = (len >> 1) + start;
	int start1 = start, end1 = mid;
	int start2 = mid + 1, end2 = end;
	merge_sort_recursive(arr, result, start1, end1);
	merge_sort_recursive(arr, result, start2, end2);
	int k = start;
	while (start1 <= end1 && start2 <= end2)
		result[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];
	while (start1 <= end1)
		result[k++] = arr[start1++];
	while (start2 <= end2)
		result[k++] = arr[start2++];
	for (k = start; k <= end; k++)
		arr[k] = result[k];
}
public static void merge_sort(int[] arr) {
	int len = arr.length;
	int[] result = new int[len];
	merge_sort_recursive(arr, result, 0, len - 1);
}

Java迭代

public static void merge_sort(int[] arr) {
	int[] orderedArr = new int[arr.length];
	for (int i = 2; i < arr.length * 2; i *= 2) {
		for (int j = 0; j < (arr.length + i - 1) / i; j++) {
			int left = i * j;
			int mid = left + i / 2 >= arr.length ? (arr.length - 1) : (left + i / 2);
			int right = i * (j + 1) - 1 >= arr.length ? (arr.length - 1) : (i * (j + 1) - 1);
			int start = left, l = left, m = mid;
			while (l < mid && m <= right) {
				if (arr[l] < arr[m]) {
					orderedArr[start++] = arr[l++];
				} else {
					orderedArr[start++] = arr[m++];
				}
			}
			while (l < mid)
				orderedArr[start++] = arr[l++];
			while (m <= right)
				orderedArr[start++] = arr[m++];
			System.arraycopy(orderedArr, left, arr, left, right - left + 1);
		}
	}
}

堆排序(Heap Sort)


介绍

堆排序是指利用堆这种数据结构所设计的一种选择排序算法。堆是一种近似完全二叉树的结构(通常堆是通过一维数组来实现的),并满足性质:以最大堆(也叫大根堆、大顶堆)为例,其中父结点的值总是大于它的孩子节点

我们可以很容易的定义堆排序的过程

  1. 由输入的无序数组构造一个最大堆,作为初始的无序区
  2. 把堆顶元素(最大值)和堆尾元素互换
  3. 把堆(无序区)的尺寸缩小1,并调maxHeapify(A, 0)从新的堆顶元素开始进行堆调整
  4. 重复步骤2,直到堆的尺寸为1
    使用堆排序为一列数字进行排序的过程
使用堆排序为一列数字进行排序的过程

代码实现

Java

import java.util.Arrays;

public class HeapSort {
    private int[] arr;
    public HeapSort(int[] arr) {
        this.arr = arr;
    }

    /**
     * 堆排序的主要入口方法,共两步。
     */
    public void sort() {
        /*
         *  第一步:将数组堆化
         *  beginIndex = 第一个非叶子节点。
         *  从第一个非叶子节点开始即可。无需从最后一个叶子节点开始。
         *  叶子节点可以看作已符合堆要求的节点,根节点就是它自己且自己以下值为最大。
         */
        int len = arr.length - 1;
        int beginIndex = (arr.length >> 1)- 1;
        for (int i = beginIndex; i >= 0; i--)
            maxHeapify(i, len);
        /*
         * 第二步:对堆化数据排序
         * 每次都是移出最顶层的根节点A[0],与最尾部节点位置调换,同时遍历长度 - 1。
         * 然后从新整理被换到根节点的末尾元素,使其符合堆的特性。
         * 直至未排序的堆长度为 0。
         */
        for (int i = len; i > 0; i--) {
            swap(0, i);
            maxHeapify(0, i - 1);
        }
    }

    private void swap(int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    /**
     * 调整索引为 index 处的数据,使其符合堆的特性。
     *
     * @param index 需要堆化处理的数据的索引
     * @param len 未排序的堆(数组)的长度
     */
    private void maxHeapify(int index, int len) {
        int li = (index << 1) + 1; // 左子节点索引
        int ri = li + 1;           // 右子节点索引
        int cMax = li;             // 子节点值最大索引,默认左子节点。
        if (li > len) return;      // 左子节点索引超出计算范围,直接返回。
        if (ri <= len && arr[ri] > arr[li]) // 先判断左右子节点,哪个较大。
            cMax = ri;
        if (arr[cMax] > arr[index]) {
            swap(cMax, index);      // 如果父节点被子节点调换,
            maxHeapify(cMax, len);  // 则需要继续判断换下后的父节点是否符合堆的特性。
        }
    }

    /**
     * 测试用例
     *
     * 输出:
     * [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9]
     */
    public static void main(String[] args) {
        int[] arr = new int[] {3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6};
        new HeapSort(arr).sort();
        System.out.println(Arrays.toString(arr));
    }
}

堆排序是不稳定的排序算法,不稳定发生在堆顶元素与A[i]交换的时刻。

比如序列:{ 9, 5, 7, 5 },堆顶元素是 9 ,堆排序下一步将 9 和第二个 5 进行交换,得到序列 { 5, 5, 7, 9 },再进行堆调整得到 { 7, 5, 5, 9 },重复之前的操作最后得到 { 5, 5, 7, 9 }从而改变了两个 5 的相对次序。

快速排序(Quick Sort)


介绍

快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序n个元素要 O ( n l o g n ) O(nlogn) O(nlogn) 次比较。在最坏状况下则需要 O ( n 2 ) O(n^2) O(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 O ( n l o g n ) O(nlogn) O(nlogn) 算法更快,因为它的内部循环可以在大部分的架构上很有效率地被实现出来。

快速排序使用分治策略(Divide and Conquer)来把一个序列分为两个子序列。步骤为:

  1. 从序列中挑出一个元素,作为"基准"(pivot)。
  2. 把所有比基准值小的元素放在基准前面,所有比基准值大的元素放在基准的后面(相同的数可以到任一边),这个称为分区(partition)操作。
  3. 对每个分区递归地进行步骤1~2,递归的结束条件是序列的大小是0或1,这时整体已经被排好序了。
    使用快速排序为一列数字进行排序的过程
使用快速排序为一列数字进行排序的过程

伪代码

function quicksort(q)
{
    var list less, pivotList, greater
    if length(q) ≤ 1 
        return q
    else 
    {
        select a pivot value pivot from q
        for each x in q except the pivot element
        {
            if x < pivot then add x to less
            if x ≥ pivot then add x to greater
        }
        add pivot to pivotList
        return concatenate(quicksort(less), pivotList, quicksort(greater))
    }
}

代码实现

Java方法一

以数组 { 49, 38, 65, 97, 76, 13, 27, 49 } 为例,选择第一个元素 49 为基准
初始化关键字: [49,38,65,97,76,13,27,49]
在这里插入图片描述

排序过程如图所示

public class QuickSort {
    public static void sort(int a[], int low, int hight) {
        int i, j, index;
        if (low > hight) {
            return;
        }
        i = low;
        j = hight;
        index = a[i]; // 用子表的第一个记录做基准
        while (i < j) { // 从表的两端交替向中间扫描
            while (i < j && a[j] >= index)
                j--;
            if (i < j)
                a[i++] = a[j];// 用比基准小的记录替换低位记录
            while (i < j && a[i] < index)
                i++;
            if (i < j) // 用比基准大的记录替换高位记录
                a[j--] = a[i];
        }
        a[i] = index;// 将基准数值替换回 a[i]
        sort(a, low, i - 1); // 对低子表进行递归排序
        sort(a, i + 1, hight); // 对高子表进行递归排序

    }

    public static void quickSort(int a[]) {
        sort(a, 0, a.length - 1);
    }

    public static void main(String[] args) {

        int a[] = { 49, 38, 65, 97, 76, 13, 27, 49 };
        quickSort(a);
        System.out.println(Arrays.toString(a));
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您提供一些关于数据结构、内部算法排序课程设计的建议。 首先,您需要确定该课程设计的目标和学习内容。您可以考虑以下几个方面: 1. 数据结构的基本原理和常用数据结构,如线性表、栈、队列、树、图等。 2. 内部算法的基本概念和分类,如贪心算法、分治算法、动态规划算法等。 3. 排序算法的基本原理和分类,如冒泡排序、选择排序、插入排序、快速排序、归并排序等。 4. 采用的编程语言和开发工具,如C++或Java编程语言,Visual Studio或Eclipse开发工具等。 5. 可能遇到的问题和解决方案,如算法复杂度分析、算法实现错误、排序算法优化等。 接下来,您可以根据以上考虑的内容,制定课程设计的详细计划。以下是一个可能的课程设计计划: 1. 第一周:介绍数据结构的基本原理和常用数据结构,讲解线性表、栈、队列、树、图等数据结构的实现和应用。 2. 第二周:学习内部算法的基本概念和分类,讲解贪心算法、分治算法、动态规划算法算法的实现和应用。 3. 第三周:介绍排序算法的基本原理和分类,讲解冒泡排序、选择排序、插入排序、快速排序、归并排序排序算法的实现和应用。 4. 第四周:实现一个基于数据结构和内部算法的实际应用,如一个简单的迷宫求解器或文本编辑器。 5. 第五周:总结和回顾整个课程设计的过程,讨论可能的改进和优化方案。 希望这些建议能够对您有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值