各种排序算法的性能分析:
![](https://i-blog.csdnimg.cn/blog_migrate/011a4b00ee42bddce45dfadbb10e1167.png)
1. 插入排序—直接插入排序(Straight Insertion Sort)
将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。
直接插入排序示例:
![](https://img-my.csdn.net/uploads/201207/17/1342520948_8667.jpg)
如果碰见一个和插入元素相等的数,那么将插入的元素放在相等元素的后面。相等元素的前后顺序没有改变,所以插入排序是稳定的。
算法实现:
#include <iostream>
using namespace std;
void print(int a[], int n,int i)
{
cout<<i <<":";
for(int j= 0; j<8; j++){
cout<<a[j] <<" ";
}
cout<<endl;
}
void InsertSort(int a[], int n)
{
for(int i = 1; i < n; i ++)
{
int x = a[i];
int j = i-1;
while( j >= 0 && x < a[j])
{
a[j+1] = a[j];
j--;
}
a[j+1] = x;
print(a,n,i);
}
}
int main()
{
int a[8] = {3,1,5,7,2,4,9,6};
InsertSort(a,8);
print(a,8,8);
}
2. 插入排序—希尔排序(Shell`s Sort)
希尔排序是1959 年由D.L.Shell 提出来的,相对直接排序有较大的改进。希尔排序又叫缩小增量排序
基本思想:
先将整个待排序列分割成若干个子序列 分别进行直接插入排序,待整个序列中的记录”基本有序“时,在对整体记录进行一次直接插入排序。
注意:子序列的构成不是简单地逐段分割,而是将相隔某个增量的记录组成一个子序列。
直接插入排序示例:
算法实现:
#include <iostream>
using namespace std;
void print(int a[], int n ,int i)
{
cout<<i <<":";
for(int j= 0; j<8; j++)
{
cout<<a[j] <<" ";
}
cout<<endl;
}
void ShellInsertSort(int a[], int n, int dk)
{
for(int i = dk; i <n; ++i)
{
int x = a[i];
int j = i - dk;
while(j >= 0 && x < a[j])
{
a[j + dk] = a[j];
j -= dk;
}
a[j+dk] = x;
print(a, n,i );
}
}
void shellSort(int a[], int n)
{
int dk = n/2;
while(dk >= 1)
{
ShellInsertSort(a, n, dk);
dk = dk/2;
}
}
int main()
{
int a[8] = {3,1,5,7,2,4,9,6};
shellSort(a,8);
print(a,8,8);
}
3. 交换排序—冒泡排序(Bubble Sort)
基本思想:
首先将第一个记录的关键字与第二个记录的关键字进行比较,如果为逆序,则将两个记录交换,然后比较第二个记录与第三个记录的关键字。以此类
推,直至第n-1个记录和第n个记录的关键字进行比较为止。上述过程为第一趟冒泡排序,其结果是使得关键字最大的记录被安置到最后一个记录的位置
上。然后进行第二趟冒泡排序,对前n-1个记录进行同样操作,其结果是使得关键字次大的记录被安置到第n-1个位置上。一般地,第 i 趟冒泡排序是从
a[0]到a[n - i]依次比较相邻两个记录的关键字,并在逆序时交换记录,其结果是这 n-i+1 个记录中关键字最大的记录被交换到第 n - i 个位置上。
冒泡排序的示例:
算法的实现:
void bubbleSort(int a[], int n)
{
for(int i = 0 ; i < n - 1; ++i)
{
for(int j = 0; j < n - i - 1; ++j)
{
if(a[j] > a[j+1])
{
int tmp = a[j] ;
a[j] = a[j+1] ;
a[j+1] = tmp;
}
}
}
}
4. 交换排序—快速排序(Quick Sort)
基本思想:
1)选择一个基准元素,通常选择第一个元素或者最后一个元素,
2)通过一趟排序将待排序的记录分割成独立的两部分。其中,一部分记录的元素值均比基准元素值小,另一部分记录的 元素值比基准值大。
3)此时基准元素在其排好序后的正确位置
4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。
快速排序的示例:
(a)一趟排序的过程:
(b)排序的全过程
算法的实现:
int partition(int a[], int low, int high)
{
int privotKey = a[low]; //基准元素
while(low < high){ //从表的两端交替地向中间扫描
while(low < high && a[high] >= privotKey) --high; //从high 所指位置向前搜索,至多到low+1 位置。将比基准元素小的交换到低端
a[low] = a[high];
while(low < high && a[low] <= privotKey ) ++low;
a[high] = a[low];
}
a[low] = privotKey;
return low;
}
void quickSort(int a[], int low, int high){
if(low < high){
int privotLoc = partition(a, low, high); //将表一分为二
quickSort(a, low, privotLoc - 1); //递归对低子表递归排序
quickSort(a, privotLoc + 1, high); //递归对高子表递归排序
}
}
5. 选择排序—简单选择排序(Simple Selection Sort)
基本思想:
在要排序的一组数中,选出最小(最大)的数与第1个位置的数交换;然后在剩下的数中,选出最小(最大)的数与第2个位置的数交换;依次类推,直到第
n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。
简单选择排序的示例:
操作方法:
第1趟,从n 个记录中找出关键码最小的记录与第一个记录交换;
第2趟,从第2个记录开始的n-1 个记录中再选出关键码最小的记录与第二个记录交换;
以此类推.....
第i 趟,从第i 个记录开始的n-i+1 个记录中选出关键码最小的记录与第i 个记录交换;
直到整个序列按关键码有序。
算法的实现:
int SelectMinKey(int a[], int n, int i)
{
int k = i;
for(int j = i + 1; j < n; ++j) {
if(a[k] > a[j]) k = j;
}
return k;
}
void selectSort(int a[], int n){
int key, tmp;
for(int i = 0; i < n - 1; ++i) {
key = SelectMinKey(a, n, i); //选择最小的元素
if(key != i){ //最小元素与第i位置元素互换
tmp = a[i];
a[i] = a[key];
a[key] = tmp;
}
}
}
6. 选择排序—堆排序(Heap Sort)
基本思想:
堆的定义:具有n个元素的序列 k1, k2, ... , kn,当且仅当满足如下条件时,称之为堆。
![](https://img-my.csdn.net/uploads/201207/18/1342589718_3742.jpg)
(a) 大顶堆序列:(96, 83,27,38,11,09)
(b) 小顶堆序列:(12,36,24,85,47,30,53,91)
初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树,调整它们的存储序,使之成为一个堆;将堆顶元素输出,得到n 个元素中最小(最大)的
元素;然后对剩下的n-1个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(次大)的元素;依此类推,直到只有两个节点的堆,并对它们
作交换,最后得到有n个节点的有序序列。称这个过程为堆排序。
调整小顶堆的方法:
1)设有m 个元素的堆,输出堆顶元素后,剩下m-1 个元素。将堆底元素送入堆顶(最后一个元素与堆顶进行交换),堆被破坏
2)将根结点与左、右子树中较小元素的进行交换。
3)若与左子树交换:如果左子树堆被破坏,即左子树的根结点不满足堆的性质,则重复方法 (2).
4)若与右子树交换,如果右子树堆被破坏,即右子树的根结点不满足堆的性质。则重复方法 (2).
5)继续对不满足堆性质的子树进行上述交换操作,直到叶子结点,堆被建成。
如图:
对n 个元素初始建堆:
1)n 个结点的完全二叉树,则最后一个结点是第个结点的子树。
2)筛选从第个结点为根的子树开始,使该子树成为堆。
3)之后向前依次对以各结点为根的子树进行筛选,使之成为堆,直到根结点。
如图:无序序列:(49,38,65,97,76,13,27,49)
算法的实现:
void HeapAdjust(int H[],int s, int m)
{
int tmp = H[s];
int child = 2*s+1; //2*s+1:左孩子 2*s+2:右孩子
while (child < m)
{
if(child + 1 < m && H[child] < H[child+1] )
{
++child ;
}
if(tmp < H[child]) //如果较大的子结点大于父结点
{
H[s] = H[child]; //把较大的子结点往上移动,替换它的父结点
s = child; //重新设置s,即待调整的下一个结点的位置
child = 2*s+1;
}
else break;
}
H[s] = tmp;
}
void HeapSort(int H[],int length)
{
for (int i = length/2-1; i >= 0; --i) //构建初始堆,最后一个非叶子的节点的位置为length/2-1
HeapAdjust(H, i, length);
for (int i = length - 1; i > 0; --i)
{
int temp = H[i]; //交换堆顶元素H[0]和堆中最后一个元素
H[i] = H[0];
H[0] = temp;
HeapAdjust(H, 0, i); //对堆进行调整
}
}
7. 归并排序(Merge Sort)
基本思想:
归并排序是将两个或两个以上的有序表合成一个新的有序表。即:把待排序序列分为若干个子序列,每个子序列是有序的,然后再把有序子序列合并为整体有序序列。
归并排序示例:
算法实现:
#include <iostream>
using namespace std;
void merge(int arr[], int tmpArray[], int lPos, int rPos, int rightEnd)
{
int i, leftEnd, NumElements, tmpPos;
leftEnd = rPos-1;
tmpPos = lPos;
NumElements = rightEnd-lPos+1;
while (lPos<=leftEnd && rPos<=rightEnd)
{
if (arr[lPos]<=arr[rPos])
{
tmpArray[tmpPos++] = arr[lPos++];
}
else
{
tmpArray[tmpPos++] = arr[rPos++];
}
}
while (lPos<=leftEnd)
{
tmpArray[tmpPos++] = arr[lPos++];
}
while (rPos<=rightEnd)
{
tmpArray[tmpPos++] = arr[rPos++];
}
for (i=0; i<NumElements; i++, rightEnd--)
{
arr[rightEnd] = tmpArray[rightEnd];
}
}
void mSort(int arr[], int tmpArray[], int left, int right)
{
int center;
if (left<right) //当left=right时,认为数组已经有序了
{
center = (left+right)/2;
mSort(arr, tmpArray, left, center);
mSort(arr, tmpArray, center+1, right);
merge(arr, tmpArray, left, center+1, right);
}
}
void mergeSort(int arr[], int n)
{
int* tmpArray = new int[n];
mSort(arr, tmpArray, 0, n-1);
delete[] tmpArray;
}
int main()
{
int arr[10] = {-1, 9, 38, 17, 16, 45, 4, 3, 2, 91};
mergeSort(arr, 10);
for (int i=0; i<10; i++) {
cout << arr[i] << " ";
}
}
8. 桶排序
基本思想:桶排序的原理是将数列分到有限数量的桶子里,对每个桶子分别排序。
当要被排序的阵列内的数值是均匀分配时候,桶排序使用线性时间(Θ(n))。但桶排序并不是比较排序,它不受到 O(n log n) 下限的影响。
例如:要对大小为[1..1000]范围内的n个整数A[1..n]排序,可以把桶设为大小为10的范围,具体而言,设集合B[1]存储[1..10]的整数,集合B[2]存储
(10..20]的整数,……集合B[i]存储((i-1)*10, i*10]的整数,i = 1,2,..100。总共有100个桶。然后对A[1..n]从头到尾扫描一遍,把每个A[i]放入对应的桶B[j]
中。 然后再对这100个桶中每个桶里的数字排序,这时可用冒泡,选择,乃至快排,一般来说任何排序法都可以。最后依次输出每个桶里面的数字,且
每个桶中的数字从小到大输出,这样就得到所有数字排好序的一个序列了。
假设有n个数字,有m个桶,如果数字是平均分布的,则每个桶里面平均有n/m个数字。如果对每个桶中的数字采用快速排序,那么整个算法的复杂
度是O(n+m*n/m*log(n/m))=O(n+nlogn-nlogm)
从上式看出,当m接近n的时候,桶排序复杂度接近O(n) 。该复杂度的计算是基于输入的n个数字是平均分布这个假设的。然而,这个假设是很强
的,实际应用中效果并没有这么好。如果所有的数字都落在同一个桶中,那就退化成一般的排序了。
9. 基数排序
基本思想:基数排序是一种借助多关键字排序的思想对单逻辑关键字进行排序的方法。实现多关键字排序的方法有:最高位优先、最低位优先。
示例:
待排数组为:73 22 93 43 55 14 28 65 39 81
1. 首先根据个位数的数值,在遍历数据时,将它们各自分配到编号0至9的桶中(个位数值与桶号一一对应),分配结果如下图所示:
2. 接下来将所有桶中数据按照桶号由小到大(桶中由顶至底)依次重新收集串起来,得到如下仍然无序的数据序列:
81 22 73 93 43 14 55 65 28 39
3. 再进行一次分配,这次根据十位数值来分配(原理同上),分配结果如下图所示:
4. 接下来再将所有桶中所盛的数据(原理同上)依次重新收集串接起来,得到如下的数据序列:
14 22 28 39 43 55 65 73 81 93
10. 外部排序
基本思想:
外部排序指的是大文件的排序,即待排序的记录存储在外存储器上,待排序的文件无法一次装入内存,需要在内存和外部存储器之间进行多次数据交换,以达到排序整个文件的目的。
外部排序最常用的算法是多路归并排序,即:将原文件分解成多个能够一次性装入内存的部分,分别把每一部分调入内存完成排序,然后,对已经排序的子文件进行多路归并排序。