常用的排序算法的时间复杂度和空间复杂度
几种排序算法视频舞蹈讲解:
http://v.youku.com/v_show/id_XMzMyODk4NTQ4.html
快速排序 http://blog.csdn.net/v_JULY_v/article/details/6262915
思想:
快速排序是冒泡排序的改进版,是目前已知的最快的排序方法。
已知一组无序数据a[1]、a[2]、……a[n],需将其按升序排列。首先任取数据a[x]作为基准。比较a[x]与其它数据并排序,使a[x]排在数据的第k位,并且使a[1]~a[k-1]中的每一个数据<a[x],a[k+1]~a[n]中的每一个数据>a[x],然后采用分治的策略分别对a[1]~a[k-1]和a[k+1]~a[n]两组数据进行快速排序。
稳定性:不稳定
时间复杂度:快速排序的最坏时间复杂度应为 O(n^2 ),最好时间复杂度为 O(n×lgn),平均时间复杂度为 O(n×lgn)。
空间复杂度:递归后所需栈空间为O(lgn) 。最坏情况下,递归树的高度为 O(n), 所需的栈空间为 O(n) 。
C语言代码实现:
// 快速排序的递归实现
//快速排序
#include<stdio.h>
voidprintList(int *list,int n)
{
int i;
for(i=0;i<n;i++)
{
printf("%d ",list[i]);
}
printf("\n");
}
voidQuickSort(int list[],int startP,int endP)
{
int i = startP , j = endP;
int key = list[startP];
if(i>j)//退出递归调用的条件
return;
while(i < j)
{
// 从后向前搜索
while(j > i && list[j]>key)
j--;
if(i<j)
list[i++] = list[j];
//printList(list,10);
// 从前向后搜索
while(i < j && list[i]<key)
i++;
if(i<j)
list[j--]=list[i];
//printList(list,10);
}
list[i]=key;
printList(list,10);
QuickSort(list, startP,i-1);
QuickSort(list, i+1, endP);
}
int main()
{
int list[10]={4,6,2,8,4,9,0,3,5,4};
int i;
printList(list,10);
QuickSort(list,0,9);
printList(list,10);
return 0;
}
堆排序
堆排序与快速排序, 归并排序一样都是时间复杂度为 O(N*logN)的几种常见排序方法。学习堆排序前,先讲解下什么是数据结构中的二叉堆。
思想:
稳定性:
时间复杂度:
空间复杂度:
C语言代码实现:
归并排序
思想:归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。 首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。
稳定性:稳定
时间复杂度:O(nlogn)
空间复杂度:
C语言代码实现:
//将有序数组a[]和b[]合并到c[]中
void MemeryArray(int a[], int n, int b[], int m, int c[])
{
int i, j, k;
i = j = k = 0;
while (i < n && j < m)
{
if (a[i] < b[j])
c[k++] = a[i++];
else
c[k++] = b[j++];
}
while (i < n)
c[k++] = a[i++];
while (j < m)
c[k++] = b[j++];
}
// 归并排序
// By MoreWindows (http://blog.csdn.net/MoreWindows)
bool MergeSort(int a[], int n)
{
int *pTempArray = new int[n];
if (p == NULL)
return false;
mergesort(a, 0, n - 1, pTempArray);
delete[] pTempArray;
return true;
}
void mergesort(int a[], int first, int last, int temp[])
{
if (first < last)
{
int mid = (first + last) / 2;
mergesort(a, first, mid, temp); //左边有序
mergesort(a, mid + 1, last, temp); //右边有序
mergearray(a, first, mid, last, temp); //再将二个有序数列合并
}
}
//将有二个有序数列a[first...mid]和a[mid...last]合并。
void mergearray(int a[], int first, int mid, int last, int temp[])
{
int i = first, j = mid + 1;
int m = mid, n = last;
int k = 0;
while (i <= m && j <= n)
{
if (a[i] < a[j])
temp[k++] = a[i++];
else
temp[k++] = a[j++];
}
while (i <= m)
temp[k++] = a[i++];
while (j <= n)
temp[k++] = a[j++];
for (i = 0; i < k; i++)
a[first + i] = temp[i];
}
归并排序的效率是比较高的,设数列长为N,将数列分开成小数列一共要logN步,每步都是一个合并有序数列的过程,时间复杂度可以记为O(N),故一共为O(N*logN)。因为归并排序每次都是在相邻的数据中进行操作,所以归并排序在O(N*logN)的几种排序方法(快速排序,归并排序,希尔排序,堆排序)也是效率比较高的。
冒泡排序
思想:已知一组无序数据a[1]、a[2]、……a[n],需将其按升序排列。首先比较a[1]与a[2]的值,若a[1]大于a[2]则交换两者的值,否则不变。再比较a[2]与a[3]的值,若a[2]大于a[3]则交换两者的值,否则不变。再比较a[3]与a[4],依此类推,最后比较a[n-1]与a[n]的值。这样处理一轮后,a[n]的值一定是这组数据中最大的。再对a[1]~a[n-1]以相同方法处理一轮,则a[n-1]的值一定是a[1]~a[n-1]中最大的。再对a[1]~a[n-2]以相同方法处理一轮,依此类推。共处理n-1轮后a[1]、a[2]、……a[n]就以升序排列了。
稳定性:稳定
时间复杂度:O(n2)
空间复杂度:O(1)
C语言代码实现:
//冒泡排序1
void BubbleSort1(int a[], int n)
{
int i, j;
for (i = 0; i < n; i++)
for (j = 1; j < n - i; j++)
if (a[j - 1] > a[j])
Swap(a[j - 1], a[j]);
}
下面对其进行优化,设置一个标志,如果这一趟发生了交换,则为true,否则为
false。明显如果有一趟没有发生交换,说明排序已经完成。
//冒泡排序2
void BubbleSort2(int a[], int n)
{
int j, k;
bool flag;
k = n;
flag = true;
while (flag)
{
flag = false;
for (j = 1; j < k; j++)
if (a[j - 1] > a[j])
{
Swap(a[j - 1], a[j]);
flag = true;
}
k--;
}
}
再做进一步的优化。如果有100个数的数组,仅前面10个无序,后面90个都已排
好序且都大于前面10个数字,那么在第一趟遍历后,最后发生交换的位置必定小
于10,且这个位置之后的数据必定已经有序了,记录下这位置,第二次只要从数
组头部遍历到这个位置就可以了。
//冒泡排序3
// By MoreWindows (http://blog.csdn.net/MoreWindows)
void BubbleSort3(int a[], int n)
{
int j, k;
int flag;
flag = n;
while (flag > 0)
{
k = flag;
flag = 0;
for (j = 1; j < k; j++)
if (a[j - 1] > a[j])
{
Swap(a[j - 1], a[j]);
flag = j;
}
}
}
选择排序
思想:已知一组无序数据a[1]、a[2]、……a[n],需将其按升序排列。首先比较a[1]与a[2]的值,若a[1]大于a[2]则交换两者的值,否则不变。再比较a[1]与a[3]的值,若a[1]大于a[3]则交换两者的值,否则不变。再比较a[1]与a[4],依此类推,最后比较a[1]与a[n]的值。这样处理一轮后,a[1]的值一定是这组数据中最小的。再将a[2]与a[3]~a[n]以相同方法比较一轮,则a[2]的值一定是a[2]~a[n]中最小的。再将a[3]与a[4]~a[n]以相同方法比较一轮,依此类推。共处理n-1轮后a[1]、a[2]、……a[n]就以升序排列了。
稳定性:不稳定
时间复杂度:O(n2)
空间复杂度:O(1)
C语言代码实现:
直接选择排序无疑是最容易实现的,下面给出代码:
// 直接选择排序
// By MoreWindows (http://blog.csdn.net/MoreWindows)
void Selectsort(int a[], int n)
{
int i, j, nMinIndex;
for (i = 0; i < n; i++)
{
nMinIndex = i; //找最小元素的位置
for (j = i + 1; j < n; j++)
if (a[j] < a[nMinIndex])
nMinIndex = j;
Swap(a[i], a[nMinIndex]); //将这个元素放到无序区的开头
}
}
在这里,要特别提醒各位注意下 Swap()的实现,建议用:
inline void Swap(int &a, int &b)
{
int c = a;
a = b;
b = c;
}
笔试面试时考不用中间数据交换二个数,很多人给出了
inline void Swap1(int &a, int &b)
{
a ^= b;
b ^= a;
a ^= b;
}
在网上搜索下,也可以找到许多这样的写法。不过这样写存在一个隐患,如果
a, b指向的是同一个数,那么调用 Swap1()函数会使这个数为 0。如:
int i = 6;
Swap1(i, i);
printf("%d\n", i);
当然谁都不会在程序中这样的写代码,但回到我们的 Selectsort(),如果 a[0]就是
最小的数,那么在交换时,将会出现将 a[0]置0 的情况,这种错误相信调试起来
也很难发现吧,因此建议大家将交换二数的函数写成:
inline void Swap(int &a, int &b)
{
int c = a;
a = b;
b = c;
}
或者在Swap1()中加个判断,如果二个数据相等就不用交换了:
inline void Swap1(int &a, int &b)
{
if (a != b)
{
a ^= b;
b ^= a;
a ^= b;
}
}
插入排序
思想:已知一组升序排列数据a[1]、a[2]、……a[n],一组无序数据b[1]、b[2]、……b[m],需将二者合并成一个升序数列。首先比较b[1]与a[1]的值,若b[1]大于a[1],则跳过,比较b[1]与a[2]的值,若b[1]仍然大于a[2],则继续跳过,直到b[1]小于a数组中某一数据a[x],则将a[x]~a[n]分别向后移动一位,将b[1]插入到原来a[x]的位置这就完成了b[1]的插入。b[2]~b[m]用相同方法插入。(若无数组a,可将b[1]当作n=1的数组a)
稳定性:稳定
时间复杂度:O(n2)
空间复杂度:O(1)
C语言代码实现:
下面给出严格按照定义书写的代码(由小到大排序):
void Insertsort1(int a[], int n)
{
int i, j, k;
for (i = 1; i < n; i++)
{
//为a[i]在前面的a[0...i-1]有序区间中找一个合适的位置
for (j = i - 1; j >= 0; j--)
if (a[j] < a[i])
break;
//如找到了一个合适的位置
if (j != i - 1)
{
//将比a[i]大的数据向后移
int temp = a[i];
for (k = i - 1; k > j; k--)
a[k + 1] = a[k];
//将a[i]放到正确位置上
a[k + 1] = temp;
}
}
}
这样的代码太长了,不够清晰。现在进行一下改写,将搜索和数据后移这二个步
骤合并。即每次a[i]先和前面一个数据a[i-1]比较,如果a[i] > a[i-1]说明a[0…i]也
是有序的,无须调整。否则就令j=i-1,temp=a[i]。然后一边将数据a[j]向后移动一
边向前搜索,当有数据a[j]<a[i]时停止并将temp放到a[j + 1]处。
void Insertsort2(int a[], int n)
{
int i, j;
for (i = 1; i < n; i++)
if (a[i] < a[i - 1])
{
int temp = a[i];
for (j = i - 1; j >= 0 && a[j] > temp; j--)
a[j + 1] = a[j];
a[j + 1] = temp;
}
}
希尔排序
思想:已知一组无序数据a[1]、a[2]、……a[n],需将其按升序排列。发现当n不大时,插入排序的效果很好。首先取一增量d(d<n),将a[1]、a[1+d]、a[1+2d]……列为第一组,a[2]、a[2+d]、a[2+2d]……列为第二组……,a[d]、a[2d]、a[3d]……列为最后一组依此类推,在各组内用插入排序,然后取d'<d,重复上述操作,直到d=1。
该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,
待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是
很高的,因此希尔排序在时间效率上比前两种方法(冒泡排序,直接插入排序)有较大提高。
稳定性:不稳定
时间复杂度:O(nlogn)~O(n2),平均时间复杂度大致是O(n√n)
空间复杂度:O(1)
C语言代码实现:
下面给出严格按照定义来写的希尔排序
void shellsort1(int a[], int n)
{
int i, j, gap;
for (gap = n / 2; gap > 0; gap /= 2) //步长
for (i = 0; i < gap; i++) //按组排序
{
for (j = i + gap; j < n; j += gap)
{
if (a[j] < a[j - gap])
{
int temp = a[j];
int k = j - gap;
while (k >= 0 && a[k] > temp)
{
a[k + gap] = a[k];
k -= gap;
}
a[k + gap] = temp;
}
}
}
}
很明显,上面的shellsort1代码虽然对直观的理解希尔排序有帮助,但代码量太大
了,不够简洁清晰。因此进行下改进和优化,以第二次排序为例,原来是每次从
1A到1E,从2A到2E,可以改成从1B开始,先和1A比较,然后取2B与2A比较,
再取1C与前面自己组内的数据比较…….。这种每次从数组第gap个元素开始,每
个元素与自己组内的数据进行直接插入排序显然也是正确的。
void shellsort2(int a[], int n)
{
int j, gap;
for (gap = n / 2; gap > 0; gap /= 2)
for (j = gap; j < n; j++) //从数组第gap个元素开始
if (a[j] < a[j - gap]) //每个元素与自己组内的数据进行直接插入排序
{
int temp = a[j];
int k = j - gap;
while (k >= 0 && a[k] > temp)
{
a[k + gap] = a[k];
k -= gap;
}
a[k + gap] = temp;
}
}
再将直接插入排序部分用《白话经典算法系列之二 直接插入排序的三种实现》
中直接插入排序的第三种方法来改写下:
//希尔排序
// By MoreWindows (http://blog.csdn.net/MoreWindows)
void shellsort3(int a[], int n)
{
int i, j, gap;
for (gap = n / 2; gap > 0; gap /= 2)
for (i = gap; i < n; i++)
for (j = i - gap; j >= 0 && a[j] > a[j + gap]; j -= gap)
Swap(a[j], a[j + gap]);
}
这样代码就变得非常简洁了。
附注:上面希尔排序的步长选择都是从 n/2 开始,每次再减半,直到最后为 1。
其实也可以有另外的更高效的步长选择,如果读者有兴趣了解,请参阅维基百科
上对希尔排序步长的说明:
http://zh.wikipedia.org/wiki/%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F