排序算法
来源:互联网 发布:软件集成规范 编辑:程序博客网 时间:2024/06/15 04:23
排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。
排序算法的稳定性
排序算法稳定性的简单形式化定义为:如果Ai = Aj,排序前Ai在Aj之前,排序后Ai还在Aj之前,则称这种排序算法是稳定的。通俗地讲就是保证排序前后两个相等的数的相对顺序不变。对于不稳定的排序算法,只要举出一个实例,即可说明它的不稳定性;而对于稳定的排序算法,必须对算法进行分析从而得到稳定的特性。需要注意的是,排序算法是否为稳定的是由具体算法决定的,不稳定的算法在某种条件下可以变为稳定的算法,而稳定的算法在某种条件下也可以变为不稳定的算法。例如,对于冒泡排序,原本是稳定的排序算法,如果将记录交换的条件改成A[i] >= A[i + 1],则两个相等的记录就会交换位置,从而变成不稳定的排序算法。其次,说一下排序算法稳定性的好处。排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,前一个键排序的结果可以为后一个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位排序后元素的顺序在高位也相同时是不会改变的。
冒泡排序
冒泡排序是一种极其简单的排序算法,也是我所学的第一个排序算法。它重复地走访过要排序的元素,依次比较相邻两个元素,如果他们的顺序错误就把他们调换过来,直到没有元素再需要交换,排序完成。这个算法的名字由来是因为越小(或越大)的元素会经由交换慢慢“浮”到数列的顶端。
冒泡排序算法的运作如下:
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;}
冒泡排序的改进:鸡尾酒排序
鸡尾酒排序,也叫定向冒泡排序,是冒泡排序的一种改进。此算法与冒泡排序的不同处在于从低到高然后从高到低,而冒泡排序则仅从低到高去比较序列里的每个元素。他可以得到比冒泡排序稍微好一点的效能。
鸡尾酒排序的代码如下:
#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;}
选择排序(Selection Sort)
选择排序也是一种简单直观的排序算法。它的工作原理很容易理解:初始时在序列中找到最小(大)元素,放到序列的起始位置作为已排序序列;然后,再从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
注意选择排序与冒泡排序的区别:冒泡排序通过依次交换相邻两个顺序不合法的元素位置,从而将当前最小(大)元素放到合适的位置;而选择排序每遍历一次都记住了当前最小(大)元素的位置,最后仅需一次交换操作即可将其放到合适的位置。
操作方法:
第一趟,从n 个记录中找出关键码最小的记录与第一个记录交换;
第二趟,从第二个记录开始的n-1 个记录中再选出关键码最小的记录与第二个记录交换;
以此类推…..
第i 趟,则从第i 个记录开始的n-i+1 个记录中选出关键码最小的记录与第i 个记录交换,
直到整个序列按关键码有序。
特征:
1、运行时间和输入无关
2、数据移动是最少的。交换的次数和数组的大小是线性关系。
选择排序的代码如下:
#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;}
选择排序是不稳定的排序算法,不稳定发生在最小元素与A[i]交换的时刻。
比如序列:{ 5, 8, 5, 2, 9 },一次选择的最小元素是2,然后把2和第一个5进行交换,从而改变了两个元素5的相对次序。
插入排序(Insertion Sort)
对于未排序数据(右手抓到的牌),在已排序序列(左手已经排好序的手牌)中从后向前扫描,找到相应位置并插入。
插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
具体算法描述如下:
1、从第一个元素开始,该元素可以认为已经被排序
2、取出下一个元素,在已经排序的元素序列中从后向前扫描
3、如果该元素(已排序)大于新元素,将该元素移到下一位置
4、重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
5、将新元素插入到该位置后
重复步骤2~5
插入排序的代码如下:
#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 InsertionSort(int A[], int n){ int length = n; for (int i = 1; i < length; ++i){ //将A[i]插入A[i-1],A[i-2],A[i-3]...之中 for (int j = i; j > 0 && A[j] < A[j - 1]; j--){ Swap(A, j, j-1); } }}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;}
插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,比如量级小于千,那么插入排序还是一个不错的选择。 插入排序在工业级库中也有着广泛的应用,在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; else left = 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;}
- 时间复杂度:O(n^2)
二分查找插入位置,因为不是查找相等值,而是基于比较查插入合适的位置,所以必须查到最后一个元素才知道插入位置。
二分查找最坏时间复杂度:当2^X>=n时,查询结束,所以查询的次数就为x,而x等于log2n(以2为底,n的对数)。即O(log2n)
所以,二分查找排序比较次数为:x=log2n
二分查找插入排序耗时的操作有:比较 + 后移赋值。时间复杂度如下:
1) 最好情况:查找的位置是有序区的最后一位后面一位,则无须进行后移赋值操作,其比较次数为:log2n 。即O(log2n)
2) 最坏情况:查找的位置是有序区的第一个位置,则需要的比较次数为:log2n,需要的赋值操作次数为n(n-1)/2加上 (n-1) 次。即O(n^2)
3) 渐进时间复杂度(平均时间复杂度):O(n^2) - 空间复杂度:O(1)
从实现原理可知,二分查找插入排序是在原输入数组上进行后移赋值操作的(称“就地排序”),所需开辟的辅助空间跟输入数组规模无关,所以空间复杂度为:O(1)
插入排序的更高效改进:希尔排序(Shell Sort)
希尔排序,也叫递减增量排序,是插入排序的一种更高效的改进版本。希尔排序是不稳定的排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率
但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位
希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。
假设有一个很小的数据在一个已按升序排好序的数组的末端。如果用复杂度为O(n^2)的排序(冒泡排序或直接插入排序),可能会进行n次的比较和交换才能将该数据移至正确位置。而希尔排序会用较大的步长移动数据,所以小数据只需进行少数比较和交换即可到正确位置。
希尔排序的代码如下:
#include <stdio.h> // 分类 -------------- 内部比较排序// 数据结构 ---------- 数组// 最差时间复杂度 ---- 根据步长序列的不同而不同。已知最好的为O(n(logn)^2)// 最优时间复杂度 ---- O(n)// 平均时间复杂度 ---- 根据步长序列的不同而不同。// 所需辅助空间 ------ O(1)// 稳定性 ------------ 不稳定void Swap(int A[], int i, int j){ int temp = A[i]; A[i] = A[j]; A[j] = temp;}void ShellSort(int A[], int n){ int length = n; int h = 0; while (h < length/3) // 生成初始增量 { h = 3 * h + 1; } while (h>=1) { for (int i = h; i < length; ++i){ //将a[i]插入a[i-h],a[i-2h],a[i-3h]...之中 for (int j = i; j >= h && A[j] < A[j - h]; j-=h){ Swap(A, j, j - h); } } h = h / 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;}
归并排序(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;}
堆排序(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;}
特点: 堆排序是已知中唯一能够同时最优的利用空间和时间的方法,在最坏的情况下他也能保证使用~2NlogN次比较和恒定的额外空间。不过现在系统很少使用它,原因是他无法利用缓存,数组很少和相邻的元素进行比较,因此缓存未命中的次数远远高于大多数比较都在相邻元素间进行的算法。
堆排序是不稳定的排序算法,不稳定发生在堆顶元素与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)来把一个序列分为两个子序列。步骤为:
从序列中挑出一个元素,作为”基准”(pivot).
把所有比基准值小的元素放在基准前面,所有比基准值大的元素放在基准的后面(相同的数可以到任一边),这个称为分区(partition)操作。
对每个分区递归地进行步骤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 i = left, j = right + 1;//左右扫描指针 int pivot = A[left];//切分元素 while (true) { //左右扫描,检查扫描是否结束并交换元素 while (A[++i] < pivot); while (pivot < A[--j]); if (i >= j)break; Swap(A, i, j); } Swap(A, left, j); return j;}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;}
算法不稳定。