排序算法作为面试考察的重点,非常有必要全面弄清楚细节,能随手写出代码。排序数据后,两个关键字相等记录保持之前的前后顺序,则称该排序算法稳定、否则称该排序算法不稳定。
一、直接插入排序
直接插入排序是最简单容易实现的一种排序算法,算法特点:
- 需要辅助空间, O(1)
- 平均时间复杂度, O(n2)
- 最差算法复杂度 ,O(n2)
- 算法稳定
//简单插入排序,排序后递增
void InsertSort(int *data, int length)
{
int i, j, key;
for(i=1; i<length; i++)
{
if(data[i] < data[i-1])
{
key = data[i];
for(j=i-1; j>=0&&key<data[j]; j=j-1) //前面数据后移
data[j+1] = data[j];
data[j+1] = key;
PrintData(data, length);
}
}
}
排序过程:
49 38 65 97 76 13 27 49 55 4
38 49 65 97 76 13 27 49 55 4
38 49 65 76 97 13 27 49 55 4
13 38 49 65 76 97 27 49 55 4
13 27 38 49 65 76 97 49 55 4
13 27 38 49 49 65 76 97 55 4
13 27 38 49 49 55 65 76 97 4
4 13 27 38 49 49 55 65 76 97
二、希尔排序
希尔排序,又名缩小增量排序,是插入排序的一种改进算法,主要将整个待排序的序列分割成为若干子序列分别进行直接插入排序,待整个序列“基本有序”,再对全体序列进行一次直接插入排序。算法特点:
- 需要辅助空间, O(1)
- 平均时间复杂度, O(nlog2n)
- 最差算法复杂度, O(nlog2n)
- 算法不稳定
//希尔排序,排序后递增
void ShellSort(int* data, int length)
{
int i, j, key, d;
for(d=length/2; d>0 ;d=d/2)
{
for(i=d; i<length; i++)//一趟希尔排序
{
if(data[i] < data[i-d])
{
key = data[i];
for(j=i-d; j>=0 && key<data[j]; j=j-d) //前面数据后移
data[j+d] = data[j];
data[j+d] = key;
PrintData(data, length);
}
}
}
}
排序过程:
49 38 65 97 76 13 27 49 55 4
13 38 65 97 76 49 27 49 55 4
13 27 65 97 76 49 38 49 55 4
13 27 49 97 76 49 38 65 55 4
13 27 49 55 76 49 38 65 97 4
13 27 49 55 4 49 38 65 97 76
4 27 13 55 49 49 38 65 97 76
4 27 13 49 49 55 38 65 97 76
4 27 13 49 38 55 49 65 97 76
4 13 27 49 38 55 49 65 97 76
4 13 27 38 49 55 49 65 97 76
4 13 27 38 49 49 55 65 97 76
4 13 27 38 49 49 55 65 76 97
三、冒泡排序
冒泡排序,第一趟从第一个数据开始,从左向右两两比较,若逆序则交换。第一趟完成后最后一个关键字最大。第二趟再从第一个数据开始,重复第一趟的过长,但只比较到 N−1 个数据。冒泡排序算法的各项性能指标与直接插入排序一致。
冒泡排序两种方法:
第一种方法,最大的后移:
void BubbleSort(int *data, int length)
{
int i, j, swap;
for(i=length-1; i>0; i--)
{
for(j=0;j<i;j++)
{
if(data[j] > data[j+1])
{
swap = data[j];
data[j] = data[j+1];
data[j+1] = swap;
}
}
}
}
排序过程:
49 38 65 97 76 13 27 49 55 4
38 49 65 76 13 27 49 55 4 97
38 49 65 13 27 49 55 4 76 97
38 49 13 27 49 55 4 65 76 97
38 13 27 49 49 4 55 65 76 97
13 27 38 49 4 49 55 65 76 97
13 27 38 4 49 49 55 65 76 97
13 27 4 38 49 49 55 65 76 97
13 4 27 38 49 49 55 65 76 97
4 13 27 38 49 49 55 65 76 97
第二种方法,最小的前移:
void BubbleSort2(int *data, int length)
{
int i, j, swap;
for(i=0; i<length-1; i++) {
for(j=length-1; j>i; j--) {
if(data[j] < data[j-1]) {
swap = data[j];
data[j] = data[j-1];
data[j-1] = swap;
}
}
PrintData(data, length);
}
}
排序过程:
49 38 65 97 76 13 27 49 55 4
4 49 38 65 97 76 13 27 49 55
4 13 49 38 65 97 76 27 49 55
4 13 27 49 38 65 97 76 49 55
4 13 27 38 49 49 65 97 76 55
4 13 27 38 49 49 55 65 97 76
4 13 27 38 49 49 55 65 76 97
4 13 27 38 49 49 55 65 76 97
4 13 27 38 49 49 55 65 76 97
4 13 27 38 49 49 55 65 76 97
四、快速排序
快速排序是冒泡排序的改进算法,是实际应用中表现最好的排序算法,也是面试必考的算法。快速排序采用了冒泡排序+二分法+迭代的算法思想。
- 需要辅助空间, O(n)
- 平均时间复杂度, O(nlog2n)
- 最差算法复杂度, O(n2)
- 算法不稳定
//快速排序
void QuickSort(int *data, int low, int high)
{
int pivotKey;
int i = low;
int j = high;
if(low < high)
{
pivotKey = data[low];
while(i < j)
{
while(i<j && data[j]>=pivotKey) j--; //从右向左找第一个小于pivotKey的数
data[i] = data[j];
while(i<j && data[i]<=pivotKey) i++; //从左向右找第一个大于pivotKey的数
data[j] = data[i];
}
data[i] = pivotKey;
QuickSort(data, low, i-1);
QuickSort(data, low+1, high);
}
}
五、堆排序
堆排序是直接选择排序的改进算法,适用于记录数较多的情况。该方法利用完全二叉树中双亲节点和孩子节点之间的内在关系,在当前无序区中选择关键字最大(或者最小)的记录。需要注意的是:如果是升序排序就使用大顶堆,反之使用小顶堆,原因是堆顶元素需要交换到序列尾部。
- 需要辅助空间, O(1)
- 平均时间复杂度, O(nlog2n)
- 最差算法复杂度, O(nlog2n)
- 算法不稳定
void HeapAdjust(int* data, int start, int end)
{
int temp = data[start];
int i = 2*start + 1;
for(; i<=end; i*=2) //左右孩子的节点分别为2*i+1, 2*i+2
{
if(i<end && data[i]<data[i+1]) i++; //i为左右孩子较大的下标
if(temp >= data[i]) break; //已经为大顶堆
data[start] = data[i]; //子节点上移
start = i; //下一轮筛选
}
data[start] = temp; //插入正确的位置
PrintData(data, 10);
}
void HeapSort(int* data, int length)
{
int i, swap;
for(i=length/2; i>=0; i--)
{
HeapAdjust(data, i, length);//大顶堆
}
printf("----------\n");
for(i=length-1; i>=0; i--) {
swap = data[0];
data[0] = data[i];
data[i] = swap;
HeapAdjust(data, 0, i-1);
}
}
排序过程:
49 38 65 97 76 13 27 49 55 4
49 38 65 97 76 13 27 49 55 4
49 38 65 97 76 13 27 49 55 4
49 38 65 97 76 13 27 49 55 4
49 38 65 97 76 13 27 49 55 4
49 97 65 49 76 13 27 38 55 4
97 65 76 49 55 13 27 38 49 4
----------
76 65 55 49 49 13 27 38 4 97
65 55 49 49 4 13 27 38 76 97
55 49 38 49 4 13 27 65 76 97
49 49 38 27 4 13 55 65 76 97
49 38 13 27 4 49 55 65 76 97
38 27 13 4 49 49 55 65 76 97
27 13 4 38 49 49 55 65 76 97
13 4 27 38 49 49 55 65 76 97
4 13 27 38 49 49 55 65 76 97
4 13 27 38 49 49 55 65 76 97
六、归并排序
归并排序使用了递归分治的思想:先递归划分子问题,然后合并结果。具体过程:把待排序列看成由两个有序的子序列,然后合并两个子序列,然后把子序列看成由两个有序序列,依次递归。最后倒着来看,先两两合并,然后四四合并,最终形成有序序列。
- 需要辅助空间, O(n)
- 平均时间复杂度, O(nlog2n)
- 最差算法复杂度, O(nlog2n)
- 算法稳定
void Merge(int* data, int left, int mid, int right, int* temp)
{
int i=left, j=mid+1, k=0;
while(i<=mid && j<=right)
{
if(data[i]<data[j])
temp[k++] = data[i++];
else
temp[k++] = data[j++];
}
while(i<=mid)
temp[k++] = data[i++];
while(j<=right)
temp[k++] = data[j++];
for(i=0; i<k; i++)
data[left+i] = temp[i];
}
void MSort(int* data, int left, int right, int* temp)
{
if(left>=right) return;
int mid = (left+right)/2;
MSort(data, left, mid, temp);
MSort(data, mid+1, right, temp);
Merge(data, left, mid, right, temp); //合并
}
void MergeSort(int* data, int length)
{
int* temp = (int*) malloc(sizeof(int)*length);
if(temp != NULL)
{
MSort(data, 0, length-1, temp);
}
free(temp);
}
排序过程
49 38 65 97 76 13 27 49 55 4
38 49 65 97 76 13 27 49 55 4
38 49 65 97 76 13 27 49 55 4
38 49 65 76 97 13 27 49 55 4
38 49 65 76 97 13 27 49 55 4
38 49 65 76 97 13 27 49 55 4
38 49 65 76 97 13 27 49 55 4
38 49 65 76 97 13 27 49 4 55
38 49 65 76 97 4 13 27 49 55
4 13 27 38 49 49 55 65 76 97