</pre><h1>1. 插入排序<h5>1.1直接插入排序</h5><span style="font-family:Microsoft YaHei;font-size:12px;">基本操作:将一个记录插入到已排好的有序表中,从而得到一个新的、记录数增1的有序表。整个排序过程进行n-1趟插入,即先将序列中第1个记录看成是一个有序的子序列,然后从第2个记录起逐个进行插入,直到整个序列变成按关键字非递减有序序列为止。性能分析:从空间来看,它只需要一个记录的辅助空间,从时间上来看排序的基本操作为比较两个关键字的大小和移动记录。当待排序列为正序则进行关键字之间的比较次数达最小值n-1,不需要移动。若待排序列为逆序总的比较次数为(n+2)(n-1)/2,移动次数为(n+2)(n-1)/2,因此直接插入排序的时间复杂度为O(n2)</span></h1><p></p><pre name="code" class="cpp">void InsertionSort(int *a, int length)
{
int i,j;
int tmp;
<p> for(i=1; i<length; i++)</p> {
tmp=a[i];
for(j=i; j>0 && a[j-1]>tmp; --j)
{
a[j]=a[j-1];
}
a[j]=tmp;
}
}
</pre><h2><span style="white-space:pre"></span>1.2.希尔排序</h2><p></p><p><span style="white-space:pre"><span style="white-space:pre"></span></span>基本操作:先将整个待排序记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录基本有序时,再对全体记录进行一次直接插入排序。<span style="white-space:pre"></span>接下来的增量序列使用Shell建议的序列。</p><p><span style="white-space:pre"><span style="white-space:pre"></span></span>性能分析:</p><p></p><pre name="code" class="cpp">void ShellSort(int *a, int length)
{
int i,j, Increment;
int tmp;
for(Increment=N/2; Increment >0; Increment /=2)
for(i=Increment; i<N; i++)
{
tmp=a[i];
for(j=i; j>=Increment; j-=Increment)
{
if(tmp<a[j-Increment])
a[j]=a[j-Increment];
else
break;
}
a[j]=tmp;
}
}
2. 交换排序
2.1 冒泡排序
基本 操作:假设待排序表长为n,从后往前(从前往后)两两比较相邻元素的值,若为逆序则交换它们,知道序列比较完。我们称它为一趟冒泡,结果将最小的元素交换到待排序列的第一个位置。下一趟冒泡时,前一趟确定的最小元素不再参与比较,这样最多做n-1趟冒泡就能把所有元素排好序。
性能分析:空间复杂度为O(1),最坏情况下时间复杂度为O(
n
2),最好情况下(表中元素基本有序)时间复杂度为O(n),其平均时间复杂度为O(
n
2)。
void BubbleSort(int *a, int n)
{//用冒泡排序法将序列a中的元素按从小到大排列
for(int i=0; i<n-1; ++i)
{
bool flag =false; //表示本趟冒泡是否发生交换
for(j=n-1; j>i; j--)
{
if(a[j-1]>a[j])
{
swap(a[j-1], a[j]);
flag=true;
}
}
if(flag==false)
return false; //本趟遍历后没有发生交换,说明表已经有序
}
}
2.2 快速排序
基本操作:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对两部分记录继续进行排序,已达到整个序列有序。首先选择一个记录作为枢轴(这里选择第一个),然后将所有关键字小于它的记录放到它的左边,所有大于它的记录放在它的右边,最后枢轴落在分界线处。
性能分析:若枢轴选择第一个元素,则当初始记录按关键字有序或基本有序时,快速排序将蜕化为起泡排序,时间复杂度为O(n2)。为改进之,统筹采用“三者取中”的法则来选择枢轴记录。总的来说快速排序的平均性能优于前面讨论的各种排序方法,平均时间复杂度为O(nlogn),最差时间复杂度为O(n2),空间复杂度为O(nlogn)。
int Partition(int *r, int low ,int high)
{
r[0]=r[low];
int pivotkey=r[low];
while(low<high)
{
while(low<high && r[high]>pivotkey) --high;
r[low]=r[hight];
while(low<high && r[low]< pivotkey) --low;
r[high]=r[low];
}
r[low]=r[0];
return low;
}
3.选择排序
3.1 简单选择排序
基本操作:每一趟(例如第i趟)在后面n-i+1(i=1, 2, ……, n-1)个待排序元素中选取关键字最小的元素,作为有序子序列的第i个元素,直到第n-1趟做完,待排序元素只剩下1个就不用再选了。
性能分析:空间复杂度为O(1),元素间比较的次数与序列的初始状态无关,始终是n(n-1)/2次,所以时间复杂度始终是O(
n
2)。
void SelectSort(int *a, int n)
{
for(int i=0; i<n-1; ++i)
{
min=i;
for(j=i+1; j<n; ++j)
{
if(a[j]<a[min])
min=j;
}
if(min!=i)
swap(a[i], a[min]);
}
}
3.2 堆排序
基本操作:先将初始文件建立一个大根堆,此时堆是无序的,然后将堆顶元素与序列的最后一个元素交换,重新调整堆,一次下去可以排序所有数据。
性能分析:堆排序方法运行时间主要消耗在建初始堆和调整建新堆时进行的反复“筛选”上,其在最坏的情况下时间复杂度为O(nlogn)。
void BuildMaxHeap(int *A, int len)
{
for(int i=len/2; i>0; --i)
{
AdjustDown(A, i, len);
}
}
void AdjustDown(int *A, int k, int len)
{
A[0]=A[k];
for(int i=2*k; i<=len; i*=2)
{
if(i<len && A[i]<A[i+1])
i++;
if(A[0]>A[i])
break;
else
{
A[k]=A[i];
k=i;
}
}
A[k]=A[0];
}
void HeapSort(int *A, int len)
{
BuildMaxHeap(A,len);
for(int i=len; i>1; i--)
{
swap(A[i], A[1]);
AdjustDown(A, 1, i-1);
}
}
4. 归并排序
基本操作:将两个或两个以上的有序表组合成一个新的有序表。该算法是经典的分治策略,它将问题分解成小的问题然后递归求解,而治的阶段将分的阶段得到的答案补到一起。
性能分析:时间复杂度和空间复杂度均为O(nlogn)。
void MSort(int *a, int *tmpArray, int left, int right)
{
int center;
if(left < right)
{
center=(left+right)/2;
MSort(A, tmpArray, left, center);
MSort(A, tmpArray,center+1, right);
merge(A,tmpArray,left, center+1, right);
}
}
void MergeSort(int *a, int length)
{
int *tmpArray;
tmpArray=malloc(length*sizeof(int));
if(tmpArray!=NULL)
{
MSort(a,tmpArray,0, length-1);
free(tmpArray);
}
else
FatalError("NO Space for tmp array!!!");
}
算法对比:
排序方法 | 平均时间 | 最坏情况 | 辅助存储 |
简单排序 | O(n2) | O(n2) | O(1) |
快速排序 | O(nlogn) | O(n2) | O(nlogn) |
堆排序 | O(nlogn) | O(nlogn) | O(1) |
归并排序 | O(nlogn) | O(nlogn) | O(n) |
基数排序 | O(d(n+r)) | O(d(n+r)) | O(r) |