基本概念
稳定性:对于序列中相等的元素a , b,且a在b的前面,若排序之后,a依然在b的前面,则该排序算法是稳定的,否则算法是不稳定的。
时间复杂度:直白的说就是算法才做的次数。
空间复杂度:指执行算法所需要的辅助空间(存储空间)。若存储空间的大小与数据的个数无关,则空间复杂度为O(1)。
说明
为方便讲清楚代码,先定义一个用于排序的顺序结构和一个交换函数。
注意:这里数字的起始位置的下标设置为1
#define MAXSIZE 10 /* 要排序数组的元素个数,可根据需要改变 */
typedef struct
{
int r [MAXSIZE +1] /* 用于存储排序数组,r[0]用作哨兵或临时变量 */
int length;
}SqList;
// 也可以使用vector替代
#include<vector>
vector<int> r(MAXSIZE+1, 0);
viod swap(SqList *L, int i , int j)
{
int temp = L->r[i];
L->r[i] = L->r[j];
L->r[j] = temp;
}
冒泡排序
冒泡算法的基本思想是:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。时间复杂度为O(n^2)
void BubbleSort(SqList* L)
{
int i, j;
flag = True;
for(i = 1; i< L->length && flag; i++)
{
flag = False;
for(j = L->length -1; j >= i; j--)
{
if(L->r[j] > L->r[j+1])
{
swap(L, j, j+1);
flag = True;
}
}
}
}
选择排序
选择排序就是通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i(1<= i <=n)个记录交换。
时间复杂度为O(n^2)。尽管选择排序与冒泡排序的时间复杂度一样,但是在性能上还是优于冒泡排序的。
void SelectSort(SqList* L)
{
int i,j, min;
for(i = 1;i< L->length;i++)
{
min = i;
for(j = i + 1;j <= L->length;i++)
{
if(L->r[min] > L->r[j])
min = j;
}
if(i != min)
swap(L, i, min);
}
}
插入排序
插入排序是将一个记录插入到已经排好顺序的有序表中,从而得到一个新的、记录数增加1的有序表。
时间复杂度同样为O(n^2), 但是优于选择排序和冒泡排序。
void InsertSort(SqList* L)
{
int i,j;
for(i = 2; i <= L->length; i++)
{
if(L->r[i] < L->r[i-1])
{
L->r[0] = L->r[i]; /* 设置哨兵 */
for( j = i -1;L->r[j] > L->r[0]; j--) /* 找出在已排序的序列中的位置 */
L->r[j+1] = L->r[j];
L->r[j+1] = L->r[0];
}
}
}
希尔排序
希尔排序是对插入排序的优化,基本思想是:将序列分割成若干个子序列,然后对这些子序列分别进行直接插入排序,当整个序列都基本有序时,注意只是基本有序时,再对全体记录进行一次直接插入排序。
所谓基本有序就是小的关键字基本在前面,大的基本在后面,不大不小的在中间。
void ShellSort(SqList* L)
{
int i, j;
int increment = L->length;
do
{
increment = increment/3 + 1;
for(i = increment+1; i< L->length;i++)
{
if(L->r[i] < L->r[i- increment])
{
L->r[0] = L->r[i];
for(j = i-increment; j>0 && L->r[0] < L->r[j]; j-= increment)
L->r[j+increment] = L->r[j];
L->r[j+increment] = L->r[0];
}
}
}
while(increment > 1);
}
堆排序
堆排序就是将待排序的序列构成一个大顶堆。此时整个序列的最大值就是堆顶的根节点。将它与堆数组的末尾元素交换,此时末尾元素就是最大值,然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素的次小值。如此反复执行,就可以得到一个有序序列。
实现堆排序需要解决两个问题:
1.如何由一个无序序列构建一个最大堆?
2.如果在输出对顶元素后,调整剩余元素成为一新的堆?
时间复杂度O(nlogn)
/* 对顺序表L 进行堆排序 */
void HeapSort(Sqort* L)
{
int i ;
for(i = L->length/2;i > 0 ;i++) /*将待排序的序列构建成一个大顶堆*/
{
HeapAdjust(L, i, L->length);
}
for(i = L->length;i>1; i--) /* 逐步将每个最大堆的根节点与末尾元素交换,并且在调整其成为大顶堆。 */
{
swap(L, l,i);
HeapAdjust(L,1,i-1);
}
}
void HeapAdjust( SqList* L, int s, int m)
{
int temp, j;
temp = L->r[s];
for(j = 2*s; j <= m; j*=2)
{
if(j < m &&L->r[j] < L->r[j+1])
j++;
if(temp >= L->r[j])
break;
L->r[s] = L->r[j];
s = j;
}
L->r[s] = temp;
}
归并排序
归并排序的原理是:假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到[n/2]个长度为1或者2 的有序子序列;再两两归并,如此重复,直至得到长度为n的有序序列为止。
时间复杂度O(nlogn),空间复杂度为O(n + logn)
void MergeSort(SqList* L)
{
MSort(L->r, L->r, 1, L->length);
}
void MSort( int SR[], int TR1[], int s, int t)
{
int m;
int TR2[MAXSIZE + 1];
if(s == t)
TR1[s] = SR[s];
else
{
m = (s+t) /2; /*将SR[s .. t]平分为SR[s..m] 和SR[m+1 .. t] */
MSort(SR,TR2, s, m); /* 递归将SR[s..m] 归并为有序的TR2[s..m] */
MSort(SR, TR2, m+1, t); /* 递归将SR[m+1 .. t] 归并为有序的TR2[m+1 .. t] */
Merge((TR2, TR1,s, m, t); /*将TR2[s..m]和TR2[m+1 .. t]归并到TR1[s..t] */
}
}
void Merge(int SR[], int TR[], int i, int m, int n)
{
int j,k,x;
for(j = m+1, k = i; i <= m && j <=n;k++)
{
if(SR[i] < SR[j])
TR[k] = SR[i++];
else
TR[k] = SR[j++];
}
if(i <= m)
{
for( x = 0; x<= m-i; x++)
TR[k+x] = SR{i+x];
}
if(j <= n)
{
for(x = 0; x<=n-j;n++)
TR[k+x] = SR[j+x];
}
}
快速排序
快速排序的基本思想是:通过一趟排序将待排序分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小, 则可分别对这两部分记录继续进行排序,已达到整个序列有序的目的。
时间复杂度O(nlogn)
void QuickSort(SqList *L)
{
QSort(L, 1,L->length);
}
void QSort(SqList* L, int low, int high)
{
int pivot;
if(low < high)
{
pivot = Partition(L,low,high);
QSort(L,low,pivot-1);
QSort(L,pivot+1,high);
}
}
int Partition(SqList* L, int low, int high)
{
int pivotkey;
pivotkey = L-> r[low];
while(low<high)
{
while(low < high&&L->r[high} >= pivotkey)
high--;
swap(L,low,high);
while(low<high && L->r[low]<=pivotkey)
low++;
swap(L,low, high);
}
return low;
}