一,插入排序
1.直接插入排序
向有序的序列中插入元素,初始时从第二个元素开始,依次将后续元素 i 插入已排好序的序列中(0 -- i)。
稳定排序 时间复杂度O(n) 空间复杂度O(1)
代码
//直接插入排序
void InsertStraight(int a[],int len)
{
int temp;
for(int i = 1; i < len; ++i)
{
temp = a[i];
int j = i - 1;
while(a[j] > temp && j >= 0)
{
a[j+1] = a[j];
--j;
}
a[j+1] = temp;
}
}
2,折半插入排序
对于待插入的数a[ i ] 来说,与上述直接插入算法不同的是,每次参与比较的是 已排好序列的中间元素 a[mid],若a[ i ] > a[mid] 则 low = mid + 1; 否则 high = mid -1 直至 low > high.
稳定排序 时间复杂度O(n) 空间复杂度O(1)
代码
//折半插入
void BinSearch(int a[], int len)
{
int temp;
for(int i = 1; i < len; ++i)
{
temp = a[i];
int low = 0;
int high = i-1;
while(high >= low)
{
int mid = (high+low)/2;
if( a[mid] > temp)
high = mid - 1;
else
low = mid + 1;
}
for(int j = i - 1; j >= high + 1; --j)
a[j+1] = a[j];
a[high + 1] = temp;
}
}
3.希尔排序
又叫作缩小增量排序,一般取初始增量为待排序列的一半, 每次缩小一半,直至为1.
不稳定排序 希尔排序的分析是一个复杂的问题,它的时间是所取“增量”序列的函数,这涉及到一些数学上尚未解决的难题,时间复杂度不稳定,较好情况下
时间复杂度为 O(n^1.3) 空间复杂度为O(1)
过程如下:
上述图片来自http://blog.csdn.net/cjf_iceking/article/details/7951481
代码
void ShellInsert(int a[], int len, int d)
{
for(int i = d; i < len; ++i)
{
int j = i - d;
int temp = a[i];
while( j>=0 && a[j]>temp )
{
a[j+d] = a[j];
j -= d;
}
a[j+d] = temp;
}
}
//希尔排序
void ShellSort(int a[], int len)
{
int d = len/2;
while(d >=1)
{
ShellInsert(a, len, d);
d /= 2;
}
}
二,交叉排序
1.冒泡排序
设置flag为优化该算法,若遍历一次没有改动则停止。
稳定排序 平均时间复杂度O(n) 空间复杂度O(1)
代码
//冒泡排序
void BubbleSort(int a[], int len)
{
bool flag;
for(int i = 1; i < len; ++i)
{
flag = false;
for(int j = 0 ; j < len - i; ++j )
{
if(a[j]>a[j+1])
{
flag = true;
int temp = a[j+1];
a[j+1] = a[j];
a[j] = temp;
}
}
if( flag==false )
break;
}
}
2,快速排序
快速排序具有最好的平均性能(average behavior),但最坏性能(worst case behavior)和插入排序相同,也是O(n^2)。比如一个序列5,4,3,2,1,要排为1,2,3,4,5。按照快速排序方法,每次只会有一个数据进入正确顺序,不能把数据分成大小相当的两份,很明显,排序的过程就成了一个歪脖子树,树的深度为n,那时间复杂度就成了O(n^2)。尽管如此,需要排序的情况几乎都是乱序的,自然性能就保证了。据书上的测试图来看,在数据量小于20的时候,插入排序具有最好的性能。当大于20时,快速排序具有最好的性能,归并(merge sort)和堆排序(heap sort)也望尘莫及,尽管复杂度都为nlg(n)。
快速排序的最坏情况基于每次划分对主元的选择。基本的快速排序选取第一个元素作为主元。这样在数组已经有序的情况下,每次划分将得到最坏的结果。一种比较常见的优化方法是随机化算法,即随机选取一个元素作为主元。这种情况下虽然最坏情况仍然是O(n^2),但最坏情况不再依赖于输入数据,而是由于随机函数取值不佳。实际上,随机化快速排序得到理论最坏情况的可能性仅为1/(2^n)。所以随机化快速排序可以对于绝大多数输入数据达到O(nlogn)的期望时间复杂度。一位前辈做出了一个精辟的总结:“随机化快速排序可以满足一个人一辈子的人品需求。”
随机化快速排序的唯一缺点在于,一旦输入数据中有很多的相同数据,随机化的效果将直接减弱。对于极限情况,即对于n个相同的数排序,随机化快速排序的时间复杂度将毫无疑问的降低到O(n^2)。解决方法是用一种方法进行扫描,使没有交换的情况下主元保留在原位置。
(以上对快速排序的总结来自博客http://www.cnblogs.com/foreverking/articles/2234225.html)
稳定排序
快速排序空间复杂度为o(lgn)(针对问题:书本上说快速排序要用深o(lgn)的栈实现递归,但是归并排序同样有递归啊,为什么归并只需o(n)的空间复杂度?)
答:
归并排序每次递归都要用到一个辅助表,长度与待排序的表长度相同,虽然递归次数是O(lgn),但每次递归都会释放掉所占的辅助空间,所以下次递归的栈空间和辅助空间与这部分释放的空间就不相关了,因而空间复杂度还是O(n)。
快速排序空间复杂度只是在通常情况下才为O(lgn),如果是最坏情况的话,很显然就要O(n)的空间了。当然,可以通过随机化选择pivot来将空间复杂度降低到O(lgn)。
代码
//快速排序
void QuickSort(int a[], int iBegin, int iEnd)
{
if(iBegin>=iEnd)
return;
int temp = a[iBegin];
int low = iBegin;
int high = iEnd;
while( low!=high )
{
while(low<high && a[high]>=temp) --high;
a[low] = a[high];
while (low<high && a[low]<= temp) ++low;
a[high] = a[low];
}
a[high] = temp;
QuickSort(a, iBegin, low-1);
QuickSort(a, low +1, iEnd);
}
三,选择排序
1,简单选择排序
每次选取第 i 个最小的数放在位置 i 上。
不稳定排序,时间复杂度O(n^2), 空间复杂度O(1)
例子:
序列5 8 5 2 9,我们知道第一遍选择第一个元素5会和2交换,那么原序列中两个5的相对前后顺序就被破坏了
代码
void SimpleSelectSort(int a[], int len)
{
for(int i = 0; i < len; ++i)
{
int min = a[i];
int t = i;
for(int j = i; j<len; j++)
if(a[j]<min)
{
min = a[j];
t = j;
}
swap(a[t],a[i]);
}
}
2,堆排序
先用给定的数构造一棵完全二叉树,然后从下标为cnt/2(cnt指给定的数的个数)开始一个一个和它的孩子结点比较,小的就往上挪,最后得到一个小顶堆,取出堆顶,并把最后一个数放入堆顶,进行同样的操作,直到所有的数都已取完为止。我们可以用一个数组来顺序地表示这棵树,左孩子可以通过2*n来找到,右孩子可以通过2*n+1来找到。
时间复杂度是O(nlgn),也是最快的排序方法之一,在最坏的情况下,其时间复杂度还是O(nlgn),相对于快速排序来说,这是堆排序的最大优点。此外,堆排序仅需要一个记录大小供交换用的辅助存储空间。堆排序也是不稳定的排序。
空间复杂度O(n)。
3,归并排序
归并的意思就是两个或两个以上的有序表组合成一个新的有序表。整个归并排序需要进行【lgn取上限】次,总的时间复杂度为O(nlgn)。与快速排序相比,归并排序的最大特点是:它是一种稳定的排序方法。
空间复杂度O(n)
代码
//2-路归并排序
void Merge(int* array, int low, int mid, int high)
{
int* temp = (int*)malloc((high - low + 1) * sizeof(int));
if (!temp) {
printf("Error:out of memory!");
return;
}
int i = low;
int j = mid + 1;
int index = 0;
while (i <= mid && j <= high) {
if (array[i] <= array[j]) {
temp[index++] = array[i++];
}
else {
temp[index++] = array[j++];
}
}
while (i <= mid) {
temp[index++] = array[i++];
}
while (j <= high) {
temp[index++] = array[j++];
}
memcpy((void*)(array + low), (void*)temp, (high - low + 1) * sizeof(int)) ;
free(temp);
}
void MergSort(int a[], int start, int end)
{
int m;
if(start<end)
{
m = (start+end)/2;
MergSort(a, start, m);
MergSort(a, m+1, end);
Merge(a, start, m, end);
}
}
4,基数排序
所谓的基数排序 其实就是一种多关键字的排序,最经典的例子就是英语字典的编排,先按第一个字母排列,分成26堆,再按第二个字母排列,……以此类推。。。