常用的排序算法-C语言

article/2025/9/30 13:32:33

目录

  • 冒泡排序  
    • 鸡尾酒排序  
  • 选择排序
  • 插入排序
    • 二分插入排序  
    • 希尔排序  
  • 归并排序
  • 堆排序
  • 快速排序

 

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

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

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

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

  这里我们来探讨一下常用的比较排序算法,非比较排序算法将在下一篇文章中介绍。下表给出了常见比较排序算法的性能:

  

 

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

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

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

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

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

 

 

  冒泡排序(Bubble Sort)

 

  冒泡排序是一种极其简单的排序算法,也是我所学的第一个排序算法。它重复地走访过要排序的元素,依次比较相邻两个元素,如果他们的顺序错误就把他们调换过来,直到没有元素再需要交换,排序完成。这个算法的名字由来是因为越小(或越大)的元素会经由交换慢慢“浮”到数列的顶端。

  冒泡排序算法的运作如下:

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

  由于它的简洁,冒泡排序通常被用来对于程序设计入门的学生介绍算法的概念。冒泡排序的代码如下:

复制代码

#include <stdio.h>// 分类 -------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- O(n^2)
// 最优时间复杂度 ---- 如果能在内部循环第一次运行时,使用一个旗标来表示有无需要交换的可能,可以把最优时间复杂度降低到O(n)
// 平均时间复杂度 ---- O(n^2)
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 稳定void Swap(int A[], int i, int j)
{int temp = A[i];A[i] = A[j];A[j] = temp;
}void BubbleSort(int A[], int n)
{for (int j = 0; j < n - 1; j++)         // 每次最大元素就像气泡一样"浮"到数组的最后{for (int i = 0; i < n - 1 - j; i++) // 依次比较相邻的两个元素,使较大的那个向后移{if (A[i] > A[i + 1])            // 如果条件改成A[i] >= A[i + 1],则变为不稳定的排序算法{Swap(A, i, i + 1);}}}
}int main()
{int A[] = { 6, 5, 3, 1, 8, 7, 2, 4 };    // 从小到大冒泡排序int n = sizeof(A) / sizeof(int);BubbleSort(A, n);printf("冒泡排序结果:");for (int i = 0; i < n; i++){printf("%d ", A[i]);}printf("\n");return 0;
}

复制代码

 

  上述代码对序列{ 6, 5, 3, 1, 8, 7, 2, 4 }进行冒泡排序的实现过程如下

    

  使用冒泡排序为一列数字进行排序的过程如右图所示:  

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

 

 

  冒泡排序的改进:鸡尾酒排序

 

  鸡尾酒排序,也叫定向冒泡排序,是冒泡排序的一种改进。此算法与冒泡排序的不同处在于从低到高然后从高到低,而冒泡排序则仅从低到高去比较序列里的每个元素。他可以得到比冒泡排序稍微好一点的效能。

  鸡尾酒排序的代码如下:

复制代码

#include <stdio.h>// 分类 -------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- O(n^2)
// 最优时间复杂度 ---- 如果序列在一开始已经大部分排序过的话,会接近O(n)
// 平均时间复杂度 ---- O(n^2)
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 稳定void Swap(int A[], int i, int j)
{int temp = A[i];A[i] = A[j];A[j] = temp;
}void CocktailSort(int A[], int n)
{int left = 0;                            // 初始化边界int right = n - 1;while (left < right){for (int i = left; i < right; i++)   // 前半轮,将最大元素放到后面{if (A[i] > A[i + 1]){Swap(A, i, i + 1);}}right--;for (int i = right; i > left; i--)   // 后半轮,将最小元素放到前面{if (A[i - 1] > A[i]){Swap(A, i - 1, i);}}left++;}
}int main()
{int A[] = { 6, 5, 3, 1, 8, 7, 2, 4 };   // 从小到大定向冒泡排序int n = sizeof(A) / sizeof(int);CocktailSort(A, n);printf("鸡尾酒排序结果:");for (int i = 0; i < n; i++){printf("%d ", A[i]);}printf("\n");return 0;
}

复制代码

 

  使用鸡尾酒排序为一列数字进行排序的过程如右图所示:  

  以序列(2,3,4,5,1)为例,鸡尾酒排序只需要访问一次序列就可以完成排序,但如果使用冒泡排序则需要四次。但是在乱数序列的状态下,鸡尾酒排序与冒泡排序的效率都很差劲。

  

  

  选择排序(Selection Sort)

 

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

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

  选择排序的代码如下:

复制代码

#include <stdio.h>// 分类 -------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- O(n^2)
// 最优时间复杂度 ---- O(n^2)
// 平均时间复杂度 ---- O(n^2)
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 不稳定void Swap(int A[], int i, int j)
{int temp = A[i];A[i] = A[j];A[j] = temp;
}void SelectionSort(int A[], int n)
{for (int i = 0; i < n - 1; i++)         // i为已排序序列的末尾{int min = i;for (int j = i + 1; j < n; j++)     // 未排序序列{if (A[j] < A[min])              // 找出未排序序列中的最小值{min = j;}}if (min != i){Swap(A, min, i);    // 放到已排序序列的末尾,该操作很有可能把稳定性打乱,所以选择排序是不稳定的排序算法}}
}int main()
{int A[] = { 8, 5, 2, 6, 9, 3, 1, 4, 0, 7 }; // 从小到大选择排序int n = sizeof(A) / sizeof(int);SelectionSort(A, n);printf("选择排序结果:");for (int i = 0; i < n; i++){printf("%d ", A[i]);}printf("\n");return 0;
}

复制代码

 

   上述代码对序列{ 8, 5, 2, 6, 9, 3, 1, 4, 0, 7 }进行选择排序的实现过程如右图  

 

  使用选择排序为一列数字进行排序的宏观过程:  

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

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

 

 

  插入排序(Insertion Sort)

 

  插入排序是一种简单直观的排序算法。它的工作原理非常类似于我们抓扑克牌

      

 

  对于未排序数据(右手抓到的牌),在已排序序列(左手已经排好序的手牌)中从后向前扫描,找到相应位置并插入。

  插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

  具体算法描述如下:

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

  插入排序的代码如下:

复制代码

#include <stdio.h>// 分类 ------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- 最坏情况为输入序列是降序排列的,此时时间复杂度O(n^2)
// 最优时间复杂度 ---- 最好情况为输入序列是升序排列的,此时时间复杂度O(n)
// 平均时间复杂度 ---- O(n^2)
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 稳定void InsertionSort(int A[], int n)
{for (int i = 1; i < n; i++)         // 类似抓扑克牌排序{int get = A[i];                 // 右手抓到一张扑克牌int j = i - 1;                  // 拿在左手上的牌总是排序好的while (j >= 0 && A[j] > get)    // 将抓到的牌与手牌从右向左进行比较{A[j + 1] = A[j];            // 如果该手牌比抓到的牌大,就将其右移j--;}A[j + 1] = get; // 直到该手牌比抓到的牌小(或二者相等),将抓到的牌插入到该手牌右边(相等元素的相对次序未变,所以插入排序是稳定的)}
}int main()
{int A[] = { 6, 5, 3, 1, 8, 7, 2, 4 };// 从小到大插入排序int n = sizeof(A) / sizeof(int);InsertionSort(A, n);printf("插入排序结果:");for (int i = 0; i < n; i++){printf("%d ", A[i]);}printf("\n");return 0;
}

复制代码

 

   上述代码对序列{ 6, 5, 3, 1, 8, 7, 2, 4 }进行插入排序的实现过程如下

      

      

  使用插入排序为一列数字进行排序的宏观过程:  

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

 

 

  插入排序的改进:二分插入排序

 

  对于插入排序,如果比较操作的代价比交换操作大的话,可以采用二分查找法来减少比较操作的次数,我们称为二分插入排序,代码如下:

复制代码

#include <stdio.h>// 分类 -------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- O(n^2)
// 最优时间复杂度 ---- O(nlogn)
// 平均时间复杂度 ---- O(n^2)
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 稳定void InsertionSortDichotomy(int A[], int n)
{for (int i = 1; i < n; i++){int get = A[i];                    // 右手抓到一张扑克牌int left = 0;                    // 拿在左手上的牌总是排序好的,所以可以用二分法int right = i - 1;                // 手牌左右边界进行初始化while (left <= right)            // 采用二分法定位新牌的位置{int mid = (left + right) / 2;if (A[mid] > get)right = mid - 1;elseleft = mid + 1;}for (int j = i - 1; j >= left; j--)    // 将欲插入新牌位置右边的牌整体向右移动一个单位{A[j + 1] = A[j];}A[left] = get;                    // 将抓到的牌插入手牌}
}int main()
{int A[] = { 5, 2, 9, 4, 7, 6, 1, 3, 8 };// 从小到大二分插入排序int n = sizeof(A) / sizeof(int);InsertionSortDichotomy(A, n);printf("二分插入排序结果:");for (int i = 0; i < n; i++){printf("%d ", A[i]);}printf("\n");return 0;
}

复制代码

 

  当n较大时,二分插入排序的比较次数比直接插入排序的最差情况好得多,但比直接插入排序的最好情况要差,所当以元素初始序列已经接近升序时,直接插入排序比二分插入排序比较次数少。二分插入排序元素移动次数与直接插入排序相同,依赖于元素初始序列。

 

 

  插入排序的更高效改进:希尔排序(Shell Sort)

 

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

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

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

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

  希尔排序的代码如下:

复制代码

#include <stdio.h>  // 分类 -------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- 根据步长序列的不同而不同。已知最好的为O(n(logn)^2)
// 最优时间复杂度 ---- O(n)
// 平均时间复杂度 ---- 根据步长序列的不同而不同。
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 不稳定void ShellSort(int A[], int n)
{int h = 0;while (h <= n)                          // 生成初始增量{h = 3 * h + 1;}while (h >= 1){for (int i = h; i < n; i++){int j = i - h;int get = A[i];while (j >= 0 && A[j] > get){A[j + h] = A[j];j = j - h;}A[j + h] = get;}h = (h - 1) / 3;                    // 递减增量}
}int main()
{int A[] = { 5, 2, 9, 4, 7, 6, 1, 3, 8 };// 从小到大希尔排序int n = sizeof(A) / sizeof(int);ShellSort(A, n);printf("希尔排序结果:");for (int i = 0; i < n; i++){printf("%d ", A[i]);}printf("\n");return 0;
}

复制代码

 

  以23, 10, 4, 1的步长序列进行希尔排序:  

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

  比如序列:{ 3, 5, 10, 8, 7, 2, 8, 1, 20, 6 },h=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(nlogn),1945年由冯·诺伊曼首次提出。

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

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

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

  归并排序的代码如下:

复制代码

#include <stdio.h>
#include <limits.h>// 分类 -------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- O(nlogn)
// 最优时间复杂度 ---- O(nlogn)
// 平均时间复杂度 ---- O(nlogn)
// 所需辅助空间 ------ O(n)
// 稳定性 ------------ 稳定void Merge(int A[], int left, int mid, int right)// 合并两个已排好序的数组A[left...mid]和A[mid+1...right]
{int len = right - left + 1;int *temp = new int[len];       // 辅助空间O(n)int index = 0;int i = left;                   // 前一数组的起始元素int j = mid + 1;                // 后一数组的起始元素while (i <= mid && j <= right){temp[index++] = A[i] <= A[j] ? A[i++] : A[j++];  // 带等号保证归并排序的稳定性}while (i <= mid){temp[index++] = A[i++];}while (j <= right){temp[index++] = A[j++];}for (int k = 0; k < len; k++){A[left++] = temp[k];}
}void MergeSortRecursion(int A[], int left, int right)    // 递归实现的归并排序(自顶向下)
{if (left == right)    // 当待排序的序列长度为1时,递归开始回溯,进行merge操作return;int mid = (left + right) / 2;MergeSortRecursion(A, left, mid);MergeSortRecursion(A, mid + 1, right);Merge(A, left, mid, right);
}void MergeSortIteration(int A[], int len)    // 非递归(迭代)实现的归并排序(自底向上)
{int left, mid, right;// 子数组索引,前一个为A[left...mid],后一个子数组为A[mid+1...right]for (int i = 1; i < len; i *= 2)        // 子数组的大小i初始为1,每轮翻倍{left = 0;while (left + i < len)              // 后一个子数组存在(需要归并){mid = left + i - 1;right = mid + i < len ? mid + i : len - 1;// 后一个子数组大小可能不够Merge(A, left, mid, right);left = right + 1;               // 前一个子数组索引向后移动}}
}int main()
{int A1[] = { 6, 5, 3, 1, 8, 7, 2, 4 };      // 从小到大归并排序int A2[] = { 6, 5, 3, 1, 8, 7, 2, 4 };int n1 = sizeof(A1) / sizeof(int);int n2 = sizeof(A2) / sizeof(int);MergeSortRecursion(A1, 0, n1 - 1);          // 递归实现MergeSortIteration(A2, n2);                 // 非递归实现printf("递归实现的归并排序结果:");for (int i = 0; i < n1; i++){printf("%d ", A1[i]);}printf("\n");printf("非递归实现的归并排序结果:");for (int i = 0; i < n2; i++){printf("%d ", A2[i]);}printf("\n");return 0;
}

复制代码

 

  上述代码对序列{ 6, 5, 3, 1, 8, 7, 2, 4 }进行归并排序的实例如下 

 

     

  使用归并排序为一列数字进行排序的宏观过程:    

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

 

 

  堆排序(Heap Sort)

  

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

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

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

  堆排序的代码如下:

复制代码

#include <stdio.h>// 分类 -------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- O(nlogn)
// 最优时间复杂度 ---- O(nlogn)
// 平均时间复杂度 ---- O(nlogn)
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 不稳定void Swap(int A[], int i, int j)
{int temp = A[i];A[i] = A[j];A[j] = temp;
}void Heapify(int A[], int i, int size)  // 从A[i]向下进行堆调整
{int left_child = 2 * i + 1;         // 左孩子索引int right_child = 2 * i + 2;        // 右孩子索引int max = i;                        // 选出当前结点与其左右孩子三者之中的最大值if (left_child < size && A[left_child] > A[max])max = left_child;if (right_child < size && A[right_child] > A[max])max = right_child;if (max != i){Swap(A, i, max);                // 把当前结点和它的最大(直接)子节点进行交换Heapify(A, max, size);          // 递归调用,继续从当前结点向下进行堆调整}
}int BuildHeap(int A[], int n)           // 建堆,时间复杂度O(n)
{int heap_size = n;for (int i = heap_size / 2 - 1; i >= 0; i--) // 从每一个非叶结点开始向下进行堆调整Heapify(A, i, heap_size);return heap_size;
}void HeapSort(int A[], int n)
{int heap_size = BuildHeap(A, n);    // 建立一个最大堆while (heap_size > 1)           // 堆(无序区)元素个数大于1,未完成排序{// 将堆顶元素与堆的最后一个元素互换,并从堆中去掉最后一个元素// 此处交换操作很有可能把后面元素的稳定性打乱,所以堆排序是不稳定的排序算法Swap(A, 0, --heap_size);Heapify(A, 0, heap_size);     // 从新的堆顶元素开始向下进行堆调整,时间复杂度O(logn)}
}int main()
{int A[] = { 5, 2, 9, 4, 7, 6, 1, 3, 8 };// 从小到大堆排序int n = sizeof(A) / sizeof(int);HeapSort(A, n);printf("堆排序结果:");for (int i = 0; i < n; i++){printf("%d ", A[i]);}printf("\n");return 0;
}

复制代码

 

  堆排序算法的演示:  

  动画中在排序过程之前简单的表现了创建堆的过程以及堆的逻辑结构。

  堆排序是不稳定的排序算法,不稳定发生在堆顶元素与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(nlogn)次比较。在最坏状况下则需要O(n^2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他O(nlogn)算法更快,因为它的内部循环可以在大部分的架构上很有效率地被实现出来。

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

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

  快速排序的代码如下:

复制代码

#include <stdio.h>// 分类 ------------ 内部比较排序
// 数据结构 --------- 数组
// 最差时间复杂度 ---- 每次选取的基准都是最大(或最小)的元素,导致每次只划分出了一个分区,需要进行n-1次划分才能结束递归,时间复杂度为O(n^2)
// 最优时间复杂度 ---- 每次选取的基准都是中位数,这样每次都均匀的划分出两个分区,只需要logn次划分就能结束递归,时间复杂度为O(nlogn)
// 平均时间复杂度 ---- O(nlogn)
// 所需辅助空间 ------ 主要是递归造成的栈空间的使用(用来保存left和right等局部变量),取决于递归树的深度,一般为O(logn),最差为O(n)       
// 稳定性 ---------- 不稳定void Swap(int A[], int i, int j)
{int temp = A[i];A[i] = A[j];A[j] = temp;
}int Partition(int A[], int left, int right)  // 划分函数
{int pivot = A[right];               // 这里每次都选择最后一个元素作为基准int tail = left - 1;                // tail为小于基准的子数组最后一个元素的索引for (int i = left; i < right; i++)  // 遍历基准以外的其他元素{if (A[i] <= pivot)              // 把小于等于基准的元素放到前一个子数组末尾{Swap(A, ++tail, i);}}Swap(A, tail + 1, right);           // 最后把基准放到前一个子数组的后边,剩下的子数组既是大于基准的子数组// 该操作很有可能把后面元素的稳定性打乱,所以快速排序是不稳定的排序算法return tail + 1;                    // 返回基准的索引
}void QuickSort(int A[], int left, int right)
{if (left >= right)return;int pivot_index = Partition(A, left, right); // 基准的索引QuickSort(A, left, pivot_index - 1);QuickSort(A, pivot_index + 1, right);
}int main()
{int A[] = { 5, 2, 9, 4, 7, 6, 1, 3, 8 }; // 从小到大快速排序int n = sizeof(A) / sizeof(int);QuickSort(A, 0, n - 1);printf("快速排序结果:");for (int i = 0; i < n; i++){printf("%d ", A[i]);}printf("\n");return 0;
}

复制代码

 

  使用快速排序法对一列数字进行排序的过程:  

 

  快速排序是不稳定的排序算法,不稳定发生在基准元素与A[tail+1]交换的时刻。

  比如序列:{ 1, 3, 4, 2, 8, 9, 8, 7, 5 },基准元素是5,一次划分操作后5要和第一个8进行交换,从而改变了两个元素8的相对次序。

 

  Java系统提供的Arrays.sort函数。对于基础类型,底层使用快速排序。对于非基础类型,底层使用归并排序。请问是为什么?

  答:这是考虑到排序算法的稳定性。对于基础类型,相同值是无差别的,排序前后相同值的相对位置并不重要,所以选择更为高效的快速排序,尽管它是不稳定的排序算法;而对于非基础类型,排序前后相等实例的相对位置不宜改变,所以选择稳定的归并排序。 

原文:http://www.cnblogs.com/eniac12/p/5329396.html


http://chatgpt.dhexx.cn/article/xUgRjR5G.shtml

相关文章

九大排序算法详解 - C语言篇

概述 排序有内部排序和外部排序&#xff0c;内部排序是数据记录在内存中进行排序&#xff0c;而外部排序是因排序的数据很大&#xff0c;一次不能容纳全部的排序记录&#xff0c;在排序过程中需要访问外存。 我们这里说说八大排序就是内部排序。 当n较大&#xff0c;则应采用时…

数据结构排序算法总结

学完数据结构已经有好长一段时间了&#xff0c;最近又重新回顾&#xff0c;做一个八大排序的总结&#xff0c;以便后期回顾。 目录 一、插入排序 1.直接插入排序 2.希尔排序 二、交换排序 1.冒泡排序 2.快速排序 三、选择排序 1.简单选择排序 2.堆排序 四、归并排序 …

程序员面试必备——动画详解十大经典排序算法(C语言版)

博客原文地址 排序算法是程序员必备的基础知识&#xff0c;弄明白它们的原理和实现很有必要。本文中将通过非常细节的动画展示出算法的原理&#xff0c;配合代码更容易理解。 概述 由于待排序的元素数量不同&#xff0c;使得排序过程中涉及的存储器不同&#xff0c;可将排序方…

【C语言八大排序思想及代码实现】

文章目录 系列文章目录前言一、冒泡排序二、选择排序三、直接插入排序四、希尔排序五、归并排序六、基数&#xff08;桶&#xff09;排序七、堆排序八、快速排序总结 一、冒泡排序 思想&#xff1a; 从第一个数开始依次向后进行比较&#xff08;第一个和第二个比较然后第二个…

经典排序算法总结(C实现)

一 排序算法介绍 1.0 排序的概述 在计算机计算和处理加工数据时&#xff0c;经常会直接或间接地涉及到数据的排序问题。可以简单地将排序操作理解为&#xff1a;将一个按值无序的数据序列转换成为一个按值有序的数据序列的过程。例如&#xff0c;将一个无序的数组 A[5] {7, …

主元排序法c语言程序,C/C++实现八大排序算法汇总

概述排序有内部排序和外部排序&#xff0c;内部排序是数据记录在内存中进行排序&#xff0c;而外部排序是因排序的数据很大&#xff0c;一次不能容纳全部的排序记录&#xff0c;在排序过程中需要访问外存。 我们这里说说八大排序就是内部排序。 当n较大&#xff0c;则应采用时间…

内排序算法小结

针对近两天所复盘的《数据结构》中的内排序部分&#xff0c;做一个小总结。尽力在最大程度上把考点重现出来&#xff0c;以便复盘之需。 本文所有算法均使用 C语言 实现。 本博客仅从个人的考点梳理为基&#xff0c;内容相较全局还有很多缺失&#xff0c;读者请权衡参考。 目…

数据结构-考研难点代码突破(C/C++/Java排序算法,性能及其稳定性分析(内部排序))

文章目录 1. 内部排序的基本种类2. 插入排序Ⅰ直接插入排序性能与稳定性分析Ⅱ 折半插入排序性能与稳定性分析Ⅲ 希尔排序性能与稳定性分析 3. 交换排序Ⅰ 冒泡排序性能与稳定性分析Ⅱ 快速排序key值选取&#xff08;三数取中&#xff09;三种交换方法① hoare左右指针② 挖坑法…

排序算法知识点总结和Java实现

排序算法知识点总结和Java实现 前言1. 术语解释2. 排序算法2.1 选择排序2.2 冒泡排序2.3 插入排序2.4 希尔排序2.5 归并排序2.6 快速排序2.7 堆排序2.8 计数排序2.9 桶排序2.10 基数排序 参考材料 前言 文章会有一些从参考材料中转载来的动图&#xff0c;如果构成侵权&#xf…

八大排序算法总结与java实现

原文链接&#xff1a; 八大排序算法总结与java实现 - iTimeTraveler 概述 因为健忘&#xff0c;加上对各种排序算法理解不深刻&#xff0c;过段时间面对排序就蒙了。所以决定对我们常见的这几种排序算法进行统一总结。首先罗列一下常见的十大排序算法&#xff1a; 直接插入排序…

C/C++最全排序算法汇总,原理+代码

1、简介 排序是计算机内经常进行的一种操作&#xff0c;其目的是将一组“无序”的记录序列调整为“有序”的记录序列。分内部排序和外部排序。若整个排序过程不需要访问外存便能完成&#xff0c;则称此类排序问题为内部排序。反之&#xff0c;若参加排序的记录数量很大&#x…

采用回调函数的内部排序算法-插入排序,希尔排序,冒泡,快排,堆排,归并排,基数排序

采用回调函数的内部排序算法-插入排序&#xff0c;希尔排序&#xff0c;冒泡&#xff0c;快排&#xff0c;堆排&#xff0c;归并排&#xff0c;基数排序 1.回调函数(callback function) :简而言之&#xff0c;回调函数就是一个通过函数指针调用的函数。 如果你把函数的指针&…

排序算法总结---C语言

排序算法总结---建议收藏 排序算法概述一&#xff1a;冒泡排序&#xff08;Bubble Sort&#xff09; 1、冒泡排序简要概述 2、冒泡排序图解 3、代码实现 二&#xff1a;选择排序&#xff08;Select Sort&#xff09; 1、选择排序简要概述 2、选择排序图解 3、代码实现 三…

C语言实现基础排序算法

排序算法平均时间复杂度最差时间复杂度空间复杂度数据对象稳定性冒泡排序O( n 2 n^{2} n2)O( n 2 n^{2} n2)O(1)稳定快速排序O(n * l o g 2 n log_{2n} log2n​)O( n 2 n^{2} n2)O( l o g 2 n log_{2n} log2n​)不稳定插入排序O( n 2 n^{2} n2)O( n 2 n^{2} n2)O(1)稳定希尔排…

排序算法-总结(c语言)

排序算法有很多种&#xff0c;但是我看网络上大多数都是十大经典排序算法&#xff0c;分别有&#xff1a; 目录 冒泡排序 1. 算法步骤 2.代码 选择排序 1. 算法步骤 2.代码 插入排序 1. 算法步骤 2.图解&#xff08;源自我主页文章&#xff09; 3.代码 希尔排序 1.…

C语言实现八种排序算法

文章目录 排序算法选择排序冒泡排序插入排序归并排序希尔排序快速排序堆排序计数排序 排序算法 平时用惯了高级语言高级工具高级算法&#xff0c;难免对一些基础算法感到生疏。但最基础的排序算法中实则蕴含着相当丰富的优化思维&#xff0c;熟练运用可起到举一反三之功效。 …

c语言八大排序算法详细版

概述排序有内部排序和外部排序&#xff0c;内部排序是数据记录在内存中进行排序&#xff0c;而外部排序是因排序的数据很大&#xff0c;一次不能容纳全部的排序记录&#xff0c;在排序过程中需要访问外存。 我们这里说说八大排序就是内部排序。 当n较大&#xff0c;则应采用时…

C语言各大排序算法整理及动画演示

&#xff08;一&#xff09;插入排序 插入排序基本思想&#xff1a;从初始的子集合开始&#xff0c;不断地将新的元素插入到已排序好的子集合当中的合适位置。&#xff08;未排序的插入到已排序当中&#xff09;具体分为直接插入排序和希尔排序两种。 ①直接插入排序 void Ins…

浅谈排序算法:冒泡排序法和选择排序法的区别

之前学习了冒泡排序法和选择排序法&#xff0c;最近被老师问某个道题用的是什么排序法。自己居然答不出来&#xff0c;才发现自己没有真正弄懂&#xff0c;这两个算法的原理和区别&#xff0c;所以 1冒泡排序法 1.1什么是冒泡排序法&#xff1f; 顾名思义&#xff0c;冒泡排…

Java语言实现常用的十种排序算法

排序问题一直都是程序工作或面试中的重点&#xff0c;特别是对于排序算法而言&#xff0c;在一些公司的笔试中&#xff0c;手写个冒泡啥的也不足为奇&#xff0c;因此今天抽空整理一下Java语言实现的各类排序算法&#xff0c;互勉学习一下。根据排序的不同方式大概可归为一下五…