您的位置:首页 > 其它

常用排序算法分析与实现

2015-10-27 15:23 423 查看
排序分为两类:内排序和外排序。

内排序:指排序过程中,待排序列全部存放在内存中处理,不需涉及数据的内、外存交换。适用于元素序列不太大的小文件。

外排序:指排序过程中,待排序列不能全部存放在内存中处理,内、外存之间需要多次进行数据交换。适用于元素序列太大,不能一次将其全部放入内存的大文件。

内排序分为六类:插入排序、交换排序、选择排序、归并排序、分配排序和计数排序。这里主要介绍前四类。

一、插入排序

插入排序是指将无序子序列中的一个或几个元素“插入”到有序子序列中。插入排序主要有直接插入排序,折半插入排序,和希尔(shell)排序。

直接插入排序:时间复杂度为O(n^2),特殊情况(原表有序)为O(n)。(稳定)

//如果升序排序

void SimpleInsertSort(int* pData, int length)

{

if(NULL == pData || length <= 0)

return ;

int i, j, temp;

for(i = 1; i < length; i++)

{//对待排元素序列进行扫描。从第二个元素开始循环到最后一个元素。

for(j = i - 1; j >= 0; j--)

{//从有序序列最后一个元素开始向前扫描,将待插入元素放入合适位置。

if(pData[j+1] > pData[j])

break;

temp = pData[j+1];

pData[j+1] = pData[j];

pData[j] = temp;

}

}

}

2)折半插入排序:由于插入排序的基本操作是在一个有序表中进行查找和插入,这个查找操作可以利用折半查找来实现,由此称之为折半插入排序。折半插入排序只是减少了元素间的比较次数,而元素的移动次数不变,因此时间复杂度为O(n^2)。(稳定)

void BinaryInsertSort(int data[], int length)

{

if(NULL == data || length <= 0)

return ;

int i, j;

for(i = 1; i < length; i++)

{//对待排元素序列进行扫描。从第二个元素开始循环到最后一个元素。

int low = 0;

int high = i - 1;

int mid = 0;

int temp = data[i]; //定义一个temp来存data[i]的值,避免移动序列将其覆盖

while(low <= high)

{//在[low...high]中折半查找出有序插入的位置,位置为high+1

mid = (low + high) >> 1;

if(data[mid] > temp)

high = mid - 1;

else

low = mid + 1;

}

for(j = i - 1; j > high; j--)

data[j+1] = data[j];

data[high+1] = temp;

}

}

3)希尔(shell)排序:是对直接插入排序的一种改进,又称“缩小增量排序”。基本思想是先将待排记录序列以一定的增量间隔d分割成多个子序列,对每个子序列分别进行一次直接插入排序,然后逐步减小增量间隔,重复上述的分组和排序,直至所取的增量为1,即所有记录放在同一组中进行直接插入排序为止。时间复杂度为O(n^(1+u))
(0 < u < 1) 。(不稳定)

//增量初始值不容易选择,代码只是参考

void ShellSort(int* pData, int length)

{

int d = length;

while(d > 1)

{

int i, temp;

d = (d + 1)>> 1;

for(i = 0; i < length - d; ++i)

{

if(pData[i] > pData[i+d])

{

temp = pData[i];

pData[i] = pData[i+d];

pData[i+d] = temp;

}

}

}

}

二、交换排序

交换排序的基本思想是两两比较待排序记录的关键字,两个记录的次序相反时即进行交换,直到没有反序的记录为止。交换排序主要有冒泡排序和快速排序。

1)冒泡排序:时间复杂度为O(n^2),特殊情况(原表有序)为O(n)。(稳定)

void BubbleSort(int data[], int length)

{

int i, j, temp;

for(i=0; i<length-1; ++i)

{ //外层for循环控制趟数,共n-1趟,大数放在最后面

for(j=0; j<length-1-i; ++j)

{//内层for循环控制相邻元素比较次数

if(data[j]>data[j+1])

{

temp = data[j];

data[j] = data[j+1];

data[j+1] = temp;

}

}

}

}

2)快速排序:是对冒泡排序的一种改进,又称“分区交换排序”。基本思想是从待排序列中任选一个记录(通常可选第一个记录),以它作为枢轴点,分别把小于和大于枢轴点的记录移到枢轴点两边。然后在两边序列中重复上述操作,直至全部序列有序。

时间复杂度是O(n*log2(n)),
特殊情况(原表有序,退化为冒泡排序)O(n^2),空间复杂度是O(log2(n))。(不稳定)

//划分算法

int Partition(int data[], int low, int high, int length)

{

if(NULL==data || low<0 || high>=length)

{

printf("输入无效参数\n");

exit(-1);

}

int temp = data[low];

while(low < high)

{

while(low < high && data[high]>=temp)

high--;

if(low < high)

data[low++] = data[high];

while(low < high && data[low]<=temp)

low++;

if(low < high)

data[high--] = data[low];

}

data[low] = temp;

return low;

}

void QuickSort(int data[], int low, int high, int length)

{

int index;

if(low < high)

{

index = Partition(data, low, high, length);

QuickSort(data, low, index-1, length);

QuickSort(data, index+1, high, length);

}

}

三、选择排序

选择排序的基本思想是每一次从待排序序列中选出最小(或最大)的一个元素,存放在已排序列的最后位置,直到全部待排序的记录排定。选择排序主要有简单选择排序和堆排序。

1)简单选择排序:时间复杂度为O(n^2)。(不稳定)

//大小到大排序

void SimpleSelectSort(int data[], int length)

{

int i, j, temp;

for(i = 0; i < length-1; ++i)

{

for(j = i+1; j < length; ++j)

{

if(data[i] > data[j])

{

temp = data[i];

data[i] = data[j];

data[j] = temp;

}

}

}

}

2)堆排序:堆实质上是一棵完全二叉树,树中任一非叶子结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。堆分为小根堆和大根堆两种。小根堆要求父结点小于等于其2个子结点;大根堆要求父结点大于等于其2个子结点。

N(N>1)个节点的的完全二叉树编号原则是:从上到下,从左自右。最后一个分枝结点(非叶子结点)的编号为 N/2
取整。且对于编号 i(1<=i<=N)有:父结点为 i/2
向下取整;若2i>N,则结点i没有左孩子,否则其左孩子为2i;若2i+1>N,则没有右孩子,否则其右孩子为2i+1。

注:使用完全二叉树只是为了好描述算法,它只是一种逻辑结构,真正在实现时我们还是使用数组来存储这棵完全二叉树的。

堆排序时间,主要由“建堆”和反复“调整”重建堆这两部分时间构成。时间复杂度为O(n*log2(n)),空间复杂度为O(1)。(不稳定)

void Swap(int* pFirstData, int* pSecondData)

{

int temp = *pFirstData;

*pFirstData = *pSecondData;

*pSecondData = temp;

}

void HeapAdjust(int data[], int startIndex, int length)

{

int i;

for(i = 2*startIndex+1; i < length; i = 2*startIndex+1)

{

if(i<length-1 && data[i]<data[i+1])

i++; //较大的记录下标

if(data[startIndex] >= data[i])

break; //不用调整了,满足堆的定义

Swap(&data[startIndex], &data[i]);

startIndex = i;

}

}

void HeapSort(int data[], int length)

{

int i;

for(i = length/2-1; i >= 0; --i)

{

//先建堆,从最后一个非叶结点开始调整,完全二叉树的最后一个非叶结点是n/2

HeapAdjust(data, i, length);

}

for(i = length-1; i >= 0; --i)

{

Swap(&data[0], &data[i]);

HeapAdjust(data, 0, i);

}

}

四、归并排序

归并排序是采用分治法的一个典型应用。该算法通过归并操作,将已有序的子序列合并,得到一个整体有序的序列。归并排序主要有二路归并排序。

1)二路归并排序:将一个具有N个待排序记录的序列看成N个长度为1的有序序列,然后进行两两归并,得到(N/2取整)个长度为2的有序序列,再进行两两归并。如此重复,直到得到一个长度为N的有序序列为止。时间复杂度是O(n*log2(n)),空间复杂度是O(n)。(稳定)

void Merge(int data[], int copy[], int first, int mid, int last)

{

int indexCopy = first;

int i = first; //
前半段第一个数字下标

int j = mid+1; //
后半段第一个数字下标

while(i <= mid && j <= last)

{

if(data[i] < data[j])

copy[indexCopy++] = data[i++];

else

copy[indexCopy++] = data[j++];

}

while(i <= mid)

{

copy[indexCopy++] = data[i++];

}

while(j <= last)

{

copy[indexCopy++] = data[j++];

}

for(i = first; i <= last; ++i)

data[i] = copy[i];

}

/***
使用递归实现 ***/

void BiMergeSort(int data[], int copy[], int first, int last)

{

if(first < last)

{

int mid = (first+last)>>1;

BiMergeSort(data, copy, first, mid);

BiMergeSort(data, copy, mid+1, last);

Merge(data, copy, first, mid, last);

}

}

外部排序最常用的是归并排序算法,它由两个阶段组成:

1) 生成初始归并段:将外存文件中的信息分段输入内存,使用内部排序算法对其进行排序,生成初始归并段,并将其写回外部文件,直至外部文件的信息全部转换成初始归并段时为止;(把原始数据分成M段,每段都排好序,分别存入M个文件中,称为顺串文件)

2) 归并:从M个顺串文件中读出头条记录,进行M路归并排序,最小的放到输出文件,同时删除对应的顺串文件中的记录。直至整个外部文件归并为单一归并段时为止。

例如:假设有一个含10000个记录的磁盘文件,而当前所用的计算机一次只能对      1,000个记录进行内部排序,则首先利用内部排序的方法得到10个初始归并段,然后进行逐趟归并。假设进行二路归并(即两两归并),则第一趟由10个归并段得到5个归并段;第二趟由
5 个归并段得到3个归并段;第三趟由3个归并段得到2个归并段;最后一趟归并得到整个记录的有序序列。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: