一、排序基本概念:
根据排序过程中待排序文件存放的位置不同,可以把排序分为内部和外部排序两大类。在排序过程中,所有需要排序的数都在内存,并在内存中调整它们的存储顺序,称为内排序;在排序过程中,只有部分数被调入内存,并借助内存调整数在外存中的存放顺序排序方法称为外排序。内部排序适用于记录个数不很多的较小待排序文件的排序;外部排序则适用于记录个数太多不能一次全部放入内存的较大待排序文件的排序。
二、内部排序分类:
- 交换排序:常用的交换排序方法有冒泡排序和快速排序。
- 选择排序:常用的选择排序方法有直接选择排序、树型选择排序和堆排序。
- 插入排序:主要的插入排序方法有直接插入排序、希尔排序、二分法插入排序、二路插入排序和共享栈插入排序等。
- 归并排序
- 基数排序
三、内部排序方法比较
1.时间性能比较
二路归并排序、堆排序和快速排序 O(nlog2n) ,
希尔排序 O(n1.5) ,
插入、冒泡、选择排序 复杂度 O(n2)
2.辅助空间的比较
二路归并排序的辅助空间为O(n),其他排序的辅助空间为O(1).
3.稳定性比较
插入排序、冒泡排序、二叉树排序、二路归并排序是稳定的。
选择排序、希尔排序、快排、堆排序是不稳定的。
4. 其它比较
插入、冒泡排序的速度较慢,但参加排序的序列局部或整体有序时,这种排序能达到较快的速度。反而在这种情况下,快速排序反而慢了。
当n较小时,对稳定性不作要求时宜用选择排序,对稳定性有要求时宜用插入或冒泡排序。
若待排序的记录的关键字在一个明显有限范围内时,且空间允许是用桶排序。
当n较大时,关键字元素比较随机,对稳定性没要求宜用快速排序。
当n较大时,关键字元素可能出现本身是有序的,对稳定性有要求时,空间允许的情况下,宜用归并排序。
当n较大时,关键字元素可能出现本身是有序的,对稳定性没有要求时宜用堆排序。
四、各种排序算法总结
1 快速排序(QuickSort)
快速排序是一个就地排序,分而治之,大规模递归的算法。从本质上来说,它是归并排序的就地版本。快速排序可以由下面四步组成。
(1) 如果不多于1个数据,直接返回。
(2) 一般选择序列最左边的值作为支点数据,或随机选择支点数据。
(3) 将序列分成2部分,一部分都大于支点数据,另外一部分都小于支点数据。
(4) 对两边利用递归排序数列。
快速排序比大部分排序算法都要快。尽管我们可以在某些特殊的情况下写出比快速排序快的算法,但是就通常情况而言,没有比它更快的了。快速排序是递归的,对于内存非常有限的机器来说,它不是一个好的选择。
2 归并排序(MergeSort)
归并排序先分解要排序的序列,从1分成2,2分成4,依次分解,当分解到只有1个一组的时候,就可以排序这些分组,然后依次合并回原来的序列中,这样就可以排序所有数据。合并排序比堆排序稍微快一点,但是需要比堆排序多一倍的内存空间,因为它需要一个额外的数组。
3 堆排序(HeapSort)
堆排序适合于数据量非常大的场合(百万数据)。
堆排序不需要大量的递归或者多维的暂存数组。这对于数据量非常巨大的序列是合适的。比如超过数百万条记录,因为快速排序,归并排序都使用递归来设计算法,在数据量非常大的时候,可能会发生堆栈溢出错误。
堆排序会将所有的数据建成一个堆,最大的数据在堆顶,然后将堆顶数据和序列的最后一个数据交换。接下来再次重建堆,交换数据,依次下去,就可以排序所有的数据。
4 Shell排序(ShellSort)
Shell排序通过将数据分成不同的组,先对每一组进行排序,然后再对所有的元素进行一次插入排序,以减少数据交换和移动的次数。平均效率是O(nlogn)。其中分组的合理性会对算法产生重要的影响。现在多用D.E.Knuth的分组方法。
Shell排序比冒泡排序快5倍,比插入排序大致快2倍。Shell排序比起QuickSort,MergeSort,HeapSort慢很多。但是它相对比较简单,它适合于数据量在5000以下并且速度并不是特别重要的场合。它对于数据量较小的数列重复排序是非常好的。
5 插入排序(InsertSort)
插入排序通过把序列中的值插入一个已经排序好的序列中,直到该序列的结束。插入排序是对冒泡排序的改进。它比冒泡排序快2倍。一般不用在数据大于1000的场合下使用插入排序,或者重复排序超过200数据项的序列。
6 冒泡排序(BubbleSort)
冒泡排序是最慢的排序算法。在实际运用中它是效率最低的算法。它通过一趟又一趟地比较数组中的每一个元素,使较大的数据下沉,较小的数据上升。它是O(n^2)的算法。
7 交换排序(ExchangeSort)和选择排序(SelectSort)
这两种排序方法都是交换方法的排序算法,效率都是 O(n2)。在实际应用中处于和冒泡排序基本相同的地位。它们只是排序算法发展的初级阶段,在实际中使用较少。
8 基数排序(RadixSort)
基数排序和通常的排序算法并不走同样的路线。它是一种比较新颖的算法,但是它只能用于整数的排序,如果我们要把同样的办法运用到浮点数上,我们必须了解浮点数的存储格式,并通过特殊的方式将浮点数映射到整数上,然后再映射回去,这是非常麻烦的事情,因此,它的使用同样也不多。而且,最重要的是,这样算法也需要较多的存储空间。
9 算法性能比较总结
下面是一个总的表格,大致总结了我们常见的所有的排序算法的特点。
排序方法 | 排序码比较次数 | 元素移动次数 | 辅助空间 | 稳定性 | 使用场合 |
---|---|---|---|---|---|
快速排序 | O( nlog2n )-O( n2 ) | O(n)~O( n2 ) | O( log2n )~O(n) | 否 | 大量无序数据,且栈空间允许 |
堆排序 | O( nlog2n ) | O( nlog2n ) | O(1) | 否 | n很大(百万级),无稳定性要求 |
归并排序 | O( nlog2n ) | O( nlog2n ) | O(n) | 是 | n很大,对稳定性有要求,空间允许 |
简单选择排序 | O( n2 ) | 0~O( n2 ) | O(1) | 否 | n较小,不要求稳定性 |
插入排序 | O(n)~O( n2 ) | 0~O( n2 ) | O(1) | 是 | n<1000,要求稳定,比冒泡快2倍 |
冒泡排序 | O( n2 ) | O( n2 ) | o(1) | 是 | 最慢,但n较小,序列局部有序是比快速排序快 |
五 各种排序算法实现
#ifndef _SORT_H
#define _SORT_H
/********************** * 各种排序算法 * author:apf * data: 2015.07.23 * ***********************/
/********************** * 序列划分 * author:spf * data: 2015.07.23 * @ describ: 以 end(最后一个元素为基准) 划分序列,升序,并返回调整后序列基准值索引 r ***********************/
//以最后基准值序列划分
int Partition(int data[], int start, int end);
//随机基准排序
int PartitionRandom(int data[], int start, int end);
/************************** * 快速排序 * data: 排序序列 * start: 排序起止地址 * end: 排序末尾地址 *思路:1.任取序列元素作为基准 2. 按照基准将序列划分为两子序列,(左序列均小于基准,右序列均大于基准) 3.分别对两个子序列重复上述方法,知道最后划分出的子序列仅有一个元素或为空。 * 时间复杂度:O(nlog2n) 理想情况和平均情况 最坏情况:O(n^2) * 空间开销:最大递归次数(存储开销) O(log2n) 最坏情况:O(n) * 适用情况:当n较大时,关键字元素比较随机,对稳定性没要求宜用快速排序。 ****************************/
void QuickSort(int data[], int start, int end);
/************************** * 选择排序(简单选择)--原地排序 * * data: 排序序列 * length:序列长度 * 思路:每一趟(i=0,1,2...n-2) 在后边的n-i个元素中找最小的元素作为有序对象的第i个对象,待到第n-2趟做完,序列只剩下一个,即排序完毕。 * 时间复杂度:O(n^2) 理想情况和平均情况 最坏情况:O(n^2) * 空间开销:O(1) * 适用情况:当n较小时,对稳定性不作要求时宜用选择排序 ****************************/
void SelectSort(int *data , int length);
/************************** * 选择排序:堆排序 * data: 排序序列 * length:序列长度 * 思路:也是一种选择排序, 堆排序会将所有的数据建成一个堆,最大的数据在堆顶,然后将堆顶数据和序列的最后一个数据交换。接下来再次重建堆,交换数据,依次下去,就可以排序所有的数据。 * 时间复杂度: 理想情况和平均情况,最坏情况均为:O(nlog2n) * 空间开销:O(1) 不稳定排序 * 适用情况:当n较大时,关键字元素可能出现本身是有序的,对稳定性没有要求时宜用堆排序。 解释: 堆排序适合于数据量非常大的场合(百万数据),堆排序不需要大量的递归或者多维的暂存数组。这对于数据量 非常巨大的序列是合适的。比如超过数百万条记录,因为快速排序,归并排序都使用递归来设计算法,在数据量 非常大的时候,可能会发生堆栈溢出错误。 ****************************/
void HeapSort(int * data, int length);
/********************堆的相关操作*******************/
int left(int i);//返回左孩子结点
int right(int i);//返回又孩子结点
int parent(int i);//返回 父节点
/********************* 函数名称: MaxHeapify 函数说明:是以i为根的子树成为最大堆(最大堆性质维护) 参数说明:data:输入数组 heapSize: 堆的大小 i:树根 **********************/
void MaxHeapify(int *data, int heapSize, int i);
/********************* 函数名称: MaxHeapify 函数说明:建最大堆 参数说明:data:输入数组 length: 数组的大小 **********************/
void BuildHeapify(int *data, int length);
/*************************** * 函数名称:Merge * 参数说明:A:待排序列数组 * p,q,r 数组下标,其中 p<<q<r; 且假设 A[p,q] 和A[q+1,r] 已排序好 * 说明: 合并排序好的序列 时间代价:O(n) n=r-p+1 */
void Merge(int* A, int p, int q, int r);
/*************************** * 函数名称:MergeSort * 参数说明:A:待排序列数组 * p,r 数组下标,其中 p<r; * 说明: 归并排序 * 思路:将代排序元素序列划分为两个长度相等的子序列,为每一个子序列排序,然后将他们合并成一个序列 * 时间代价:O(nlog2n) n=r-p+1 额外空间:O(n) 元素移动次数:2nlog2n 稳定排序 * 适用场合:归并排序比堆排序稍微快一点,但是需要比堆排序多一倍的内存空间,因为它需要一个额外的数组, 当n较大时,关键字元素可能出现本身是有序的,对稳定性有要求时,空间允许的情况下,宜用归并排序。 */
void MergeSort(int* A, int p, int r);
/*************************** * 函数名称:InsertSort * 参数说明:A:待排序列数组 * length:数组长度; *说明: 插入排序 *思路:每步将一个待排元素按其排序码大小插入到前面已经排序好的一组元素的适当位置,直到元素全部插入为止。 具体为:把数组分为有序序列和无序序列,开始时,A[0]为有序序列,A[1]....A[n-1]为无序序列,每次冲无序序列中取出一个元素插入到有序序列合适位置,经过n-1次出入,无序序列为空,排序完毕。 *时间代价:O(n^2) 额外空间:O(1) 稳定排序 少量元素排序合适 * 使用场合:当n较小时,对稳定性有要求时宜用插入或冒泡排序, 解释:插入排序是对冒泡排序的改进。它比冒泡排序快2倍。一般不用在数据大于1000的场合下使用插入排序,或者重复排序超过200数据项的序列 **/
void InsertSort(int* A,int length);
/*************************** * 函数名称:BubbleSort * 参数说明:A:待排序列数组 * length:数组长度; *说明: 冒泡排序 *思路:冒泡排序是最慢的排序算法。在实际运用中它是效率最低的算法。它通过一趟又一趟地比较数组中的每一个元素,使较大的数据下沉,较小的数据上升。 可调终点起泡法,从前往后开始起泡,将最大值逐渐后移,设置变量k,跟踪每次最后交换的结点A[i+1] A[i] k=i,下一趟棋起泡的终点即为k *时间代价:O(n^2) 额外空间:O(1) 稳定排序 少量元素排序合适 * 使用情况:冒泡排序是最慢的排序算法,当n较小时,对稳定性有要求时宜用插入或冒泡排序 解释:插入、冒泡排序的速度较慢,但参加排序的序列局部或整体有序时,这种排序能达到较快的速度。反而在这种 情况下,快速排序反而慢了 **/
void BubbleSort(int* A, int length);
void BubbleSortFix(int* A, int length);//固定终点起泡法
#endif /*sort.h*/
#include "stdafx.h"
#include "sort.h"
#include <exception>
#include <algorithm>
#include <climits>
using namespace std;
int Partition(int data[], int start, int end)
{
if (data == nullptr || start < 0 || end < 0 || end - start < 0)
throw new exception("Invalid Parameters");
int pivot = data[end];//取出基准值
int i = start - 1;
for (int j = start; j < end;++j)
{
//if (data[j] < pivot)//升序排列
if (data[j] > pivot)//降序排列
{
++i;
if (i!=j)
swap(data[i], data[j]);//使用标准库里边的swap函数
}
}
++i;
swap(data[i], data[end]);//调整基准值位置
return i;
}
int PartitionRandom(int data[], int start, int end)
{
if (data == nullptr || start < 0 || end < 0 || end - start < 0)
throw new exception("Invalid Parameters");
int pivot = start + rand()%(end-start+1);//取[start,end]之间的随机数
swap(data[pivot], data[end]);//转载基准值到序列末尾
return Partition(data, start, end);
}
void QuickSort(int data[], int start, int end)
{
if (data == nullptr || start < 0 || end < 0 || end - start < 0)
throw new exception("Invalid Parameters");
//if (start == end)//只有一个元素
// return ;
if (start<end)
{
int pivot = PartitionRandom(data, start, end);
if (pivot > start)
QuickSort(data, start, pivot-1);
if (pivot < end)
QuickSort(data,pivot+1,end);
}
}
/***************************** * 函数名称:SelectSort * 参数说明:data: 无序数组 length:数组长度 * 说明: 简单选择排序 *****************************/
void SelectSort(int *data, int length)
{
if (data == nullptr || length < 1)
return;
for (int i = 0; i < length - 1; ++i)
{
int index=i;//跟踪最小元素位置
for (int j = i + 1; j < length; ++j)
{
if (data[j] < data[index])
index = j;
}
if (i != index)//如果最小位置发生变化,则调换位置
swap(data[i], data[index]);
}
}
int left(int i)
{
return 2*i+1;
}
int right(int i)
{
return 2 * i + 2;
}
int parent(int i)
{
return (i - 1) / 2;
}
void MaxHeapify(int *data, int heapSize, int i)
{
int lc = left(i);//左孩子结点
int rc = right(i);//右孩子结点
int largest;
if (lc < heapSize&& data[lc] > data[i])//左孩子大于根节点
largest = lc;
else
largest = i;
if (rc < heapSize&&data[rc] > data[largest])//若又孩子最大
largest = rc;
if (largest != i)//若根不是最大结点
{
swap(data[i], data[largest]);
MaxHeapify(data, heapSize, largest);//重新维护其孩子的最大堆性质
}
}
void BuildHeapify(int *data, int length)
{
if (data == nullptr || length < 1)
return;
for (int i = length / 2 - 1; i >=0 ;--i)//由于数组A[length/2....n-1]的元素都是树中的叶子,没有孩子,因此
//自低向上将数组data维护成最大堆
{
MaxHeapify(data, length, i);
}
}
void HeapSort(int * data, int length)
{
if (data == nullptr || length < 1)
{
throw new exception("The array does not exist or is empty");
return;
}
BuildHeapify(data, length);
for (int i = length - 1; i > 0;--i)
{
swap(data[i], data[0]);//每次将最大元素(堆根)与堆底元素交换
--length;//堆大小减一
MaxHeapify(data, length, 0);//从堆根部重新维护性质
}
}
void Merge(int* A, int p, int q, int r)
{
if (A == nullptr || p < 0 || q < p || r < q)
{
throw new exception("Invalid input parameters");
return;
}
int n1 = q - p + 1;
int n2 = r - q;
int *L = new int[n1+1]();
int *R = new int[n2 + 1]();
for (int m = 0; m < n1;++m)
{
L[m] = A[p + m];
}
for (int i = 0; i < n2; i++)
{
R[i] = A[q + i + 1];
}
L[n1] = INT_MAX;
R[n2] = INT_MAX;
int i = 0;
int j = 0;
for (int k = p; k <= r;++k)
{
if (L[i] < R[j])
{
A[k] = L[i];
++i;
}
else
{
A[k] = R[j];
++j;
}
}
}
void MergeSort(int* A, int p, int r)
{
if (A == nullptr || p < 0 || r < p)
{
throw new exception("Invalid input parameters in function of MergeSort");
return;
}
if (p < r)
{
int q = (p + r) / 2;
MergeSort(A, p, q);
MergeSort(A, q + 1, r);
Merge(A, p, q, r);
}
}
void InsertSort(int* A, int length)
{
if (A == nullptr || length < 1)
return;
for (int i = 1; i < length;++i)//将A[1....n-1]一次插入到序列的正确位置
{
int key = A[i];
int j = i - 1;
while (j>=0&&A[j]>key)//将大于key的元素一次后移,
{
A[j + 1] = A[j];
--j;
}
A[j + 1] = key;//直到站到key合适位置
}
}
void BubbleSort(int* A, int length)
{
int k = length - 1,i,j;//起泡终点标记,k初始化为n-1
while (k>0)
{
j = 0;//记录交换位置
for (i = 0; i < k;++i)
{
if (A[i]>A[i + 1])//发生逆序
{
swap(A[i], A[i + 1]);//交换
j = i;//记录交换位置
}
}
k = j;//记录最后交换位置
}
}
void BubbleSortFix(int* A, int length)
{
for (int j = length - 1; j > 1;--j)//控制起泡终点
{
bool isBubble = false;//是否起泡标志
for (int i = 0; i < j;++i)//从前向后开始比较,起泡
{
if (A[i]>A[i + 1])
{
swap(A[i], A[i + 1]);
isBubble = true;
}
}
if (!isBubble)//未发生逆序,则排序完成
return;
}
}
算法测试:
// sort.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "sort.h"
#include <iostream>
#include <exception>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
int a[] = { 1, 3, 5, 2, 4, 9, 0, 8, 7, 6 };
try
{
//QuickSort(a, 0, 9);
//SelectSort(a, 10);
//HeapSort(a, 10);
//MergeSort(a, 0, 9);
//InsertSort(a, 10);
//BubbleSort(a, 10);
BubbleSortFix(a, 10);
for(int i=0;i<10;++i)
{
cout << a[i] << endl;
}
}
catch (exception* e)
{
cout<<e->what()<<endl;
}
system("pause");
return 0;
}
1:http://www.cnblogs.com/aiyelinglong/archive/2012/03/28/2421473.html
参考2:http://blog.chinaunix.net/uid-26565142-id-3126683.html