(一)排序算法的分类
(1)按照是否完全在内存中分为
外部排序:排序期间在内外存之间来回切换
内部排序:排序期间元素全部放在内存中的排序
(2)按照重复关键字
稳定排序:如果排序之前ai = aj; ai在aj前面,排序之后,ai仍然在aj前面
非稳定排序:与稳定排序的算法刚好相反
(3)影响性能的因素
比较次数和交换次数
辅助空间
(4)下面是各种排序方法的介绍
冒泡排序
基本思路:两两关键字进行比较,如果反序就交换,直到整个序列没有反序的记录为止
最简单的冒泡排序的代码:
#include<iostream>
using namespace std;
int main()
{
int a[10] = {2,3,1,10,9,7,8,9,2,6};
int n = sizeof(a)/sizeof(a[0]);
for(int i=0; i<n; ++i)
{
for(int j = i+1; j<n; ++j)
{
if(a[j] > a[i])
{
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
}
}
for(int i=0; i<n; ++i)
{
cout<<a[i]<<" ";
}
}
下面是正宗的冒泡排序
#include<iostream>
using namespace std;
int main()
{
int a[10] = {2,3,1,10,9,7,8,9,2,6};
int n = sizeof(a)/sizeof(a[0]);
for(int i=0; i<n; ++i)
{
for(int j = 0; j<n-i-1; ++j)
{
if(a[j] > a[j+1])
{
int tmp = a[j+1];
a[j+1] = a[j];
a[j] = tmp;
}
}
}
for(int i=0; i<n; ++i)
{
cout<<a[i]<<" ";
}
}
比如说是这种情况:
2 1 3 4 5 6 7 8 9
只需要交换前两个元素就可以,但是冒泡把所有的循环全部执行了一遍
下面对冒泡排序进行改造:
用一个flag值作标记,如果交换就该为true
#include<iostream>
using namespace std;
int main()
{
int a[10] = {2,3,1,10,9,7,8,9,2,6};
int n = sizeof(a)/sizeof(a[0]);
bool flag = true;
for(int i=0; i<n; ++i)
{
flag = false;
for(int j = 0; j<n-i-1; ++j)
{
if(a[j] > a[j+1])
{
int tmp = a[j+1];
a[j+1] = a[j];
a[j] = tmp;
flag = true;
}
}
}
for(int i=0; i<n; ++i)
{
cout<<a[i]<<" ";
}
冒泡排序的复杂度分析:
最好的情况:比较n-1次
最坏的情况:比较n(n-1)/2次
平均时间复杂度为O(n^2)
空间复杂度为O(1)
基本思想:通过n-1次关键字的比较,从n-i+1个记录中找出关键字最小的记录,并和第i个记录进行交换
9 1 5 8 3 7 4 6 2
i=0;
从9 后面的元素中找出最小的元素和9交换
1 9 5 8 3 7 4 6 2
i=1;
从9后面的元素中找出最小的元素和9交换
1 2 5 8 73 7 4 6 9
这样继续寻找
......
代码实现:
#include<iostream>
using namespace std;
void sort(int a[], int n)
{
int i,j,min;
for(i=0; i<n; ++i)
{
min = i;
for(j = i+1; j<n; ++j)
{
if(a[min] > a[j])
{
min = j;
}
}
if(i != min)
{
int tmp = a[i];
a[i] = a[min];
a[min] = tmp;
}
}
}
int main()
{
int a[10] = {2,3,1,10,9,7,8,9,2,6};
int n = sizeof(a)/sizeof(a[0]);
sort(a, n);
for(int i=0; i<n; ++i)
{
cout<<a[i]<<" ";
}
}
简单选择排序 的复杂度分析:
(1)比较次数:第i 趟需要n-次比较,一共需要n(n-1)/2;
(2)交换次数:最好的情况0次
最坏的情况n-1次
最终的时间复杂度为O(n^2),但是性能上优于冒泡
空间复杂度为O(1)
直接插入排序:
将一个新的记录插入到已经排好序的有序表里,得到一个新的,记录增1的序列
1 4 2 5 7 8 3 0 1 6
1 4
1 2 4
1 2 4 5
1 2 4 5 7
1 2 4 7 8
1 2 3 4 5 7 8
代码实现:
#include<iostream>
using namespace std;
void sort(int a[], int n)
{
int i,j,tmp;
for(i=1; i<n; ++i)
{
if(a[i-1] > a[i])
{
tmp = a[i];
for(j=i-1; a[j] > tmp; --j)
{
a[j+1] = a[j];
}
a[j+1] = tmp;
}
}
}
int main()
{
int a[10] = {2,3,1,10,9,7,8,9,2,6};
int n = sizeof(a)/sizeof(a[0]);
sort(a, n);
for(int i=0; i<n; ++i)
{
cout<<a[i]<<" ";
}
}
时间复杂度为O(n^2)
希尔排序:
算法思想:将相聚某个增量的记录组成一个子序列,分别进行直接插入
increment = length/3+1
不是随便分组后各自排序,而是将相隔某个增量的记录组成一个子序列,实现跳跃式的移动
时间复杂度o(n^3/2)
空间复杂度为O(1)
代码:
#include<iostream>
using namespace std;
void sort(int a[], int n)
{
int i,j,tmp;
int increment = n;
do
{
increment = increment/3+1;
for(i=increment; i<n; ++i)
{
if(a[i] < a[i-increment])
{
tmp = a[i];
for(j=i-increment; j>=0 && tmp < a[j]; j-=increment)
{
a[j+increment] = a[j];
}
a[j+increment] = tmp;
}
}
}while(increment > 1);
}
int main()
{
int a[10] = {2,3,1,10,9,7,8,9,2,6};
int n = sizeof(a)/sizeof(a[0]);
sort(a, n);
for(int i=0; i<n; ++i)
{
cout<<a[i]<<" ";
}
}
堆排序:
堆是具有以下性质的完全二叉树的结构
大顶堆:每个结点的值都大于或者等于它的左右孩子结点
小顶堆:每个结点的值小于或者等于它的左右孩子的结点
根节点一定是所有结点中中的最大者或者最小者,较大者或者较小者的节点靠近根结点
堆排序的思想:
将待排序的序列构成一个大顶堆,整个序列的最大值就是堆顶的根结点,把它移走(就是与堆数组的末尾元素进行交换,此时末尾的元素就是最大值,然后将n-1个序列重新构成一个堆,这样就可以得到n个元素的次小元素,这样反复进行,就会得到一个有序的序列)
#include<iostream>
using namespace std;
void HeapAdjust(int a[], int i, int n)
{
int tmp,j;
tmp = a[i];
for(j=2*i; j<=n; j*=2)
{
if(j<n && a[j] < a[j+1])
++j;
if(tmp >= a[j])
break;
a[i] = a[j];
i = j;
}
a[i] = tmp;
}
void sort(int a[], int n)
{
int i;
for(i=n/2;i>=0;--i)
{
HeapAdjust(a, i, n);
}
for(int i=n-1; i>=0; --i)
{
int tmp = a[i];
a[i] = a[0];
a[0] = tmp;
HeapAdjust(a, 0, i-1);
}
}
int main()
{
int a[10] = {2,3,1,10,9,7,8,9,2,6};
int n = sizeof(a)/sizeof(a[0]);
sort(a, n);
for(int i=0; i<n; ++i)
{
cout<<a[i]<<" ";
}
}
构建堆O(n)
重建堆O(n*logn)
总体:时间复杂度:O(n*logn);
空间复杂度:O(1)
归并排序:
两两归并排序
代码:
#include<iostream>
using namespace std;
#define MAXSIZE 30
void Merge(int num[], int tmp[], int first, int mid, int end)
{
int j,k,l;
for(j=mid+1,k=first;first<=mid && j<=end; k++)
{
if(num[first] < num[j])
tmp[k] = num[first++];
else
tmp[k] = num[j++];
}
if(first <= mid)
{
for(l=0; l<=mid-first; ++l)
{
tmp[k+l] = num[first+l];
}
}
if(j<=end)
{
for(l=0; l<=end-j; ++l)
{
tmp[k+l] = num[j+l];
}
}
}
void sort(int num[], int merge[], int first, int end)
{
int mid;
int tmp[MAXSIZE+1];
if(first == end)
{
merge[first] = num[first];
}
else
{
mid = (first + end)/2;
sort(num, tmp, first, mid);
sort(num, tmp, mid+1, end);
Merge(tmp,merge, first, mid, end);
}
}
int main()
{
int a[10] = {2,3,1,10,9,7,8,9,2,6};
int n = sizeof(a)/sizeof(a[0]);
sort(a,a,0,n-1);
for(int i=0; i<n; ++i)
{
cout<<a[i]<<" ";
}
}
时间复杂度:
O(n* logn)
空间复杂度为O(n)
下面是用非递归的方法实现:
#include<iostream>
using namespace std;
#define MAXSIZE 30
void Merge(int num[], int tmp[], int first, int mid, int end)
{
int j,k,l;
for(j=mid+1,k=first;first<=mid && j<=end; k++)
{
if(num[first] < num[j])
tmp[k] = num[first++];
else
tmp[k] = num[j++];
}
if(first <= mid)
{
for(l=0; l<=mid-first; ++l)
{
tmp[k+l] = num[first+l];
}
}
if(j<=end)
{
for(l=0; l<=end-j; ++l)
{
tmp[k+l] = num[j+l];
}
}
}
void MergePass(int num[], int tmp[], int first, int end)
{
int i=0;
int j;
while(i<end-2*first+1)
{
Merge(num, tmp, i,i+first-1, i+2*first-1);
i = i+2*first;
}
if(i<end-first+1)
Merge(num, tmp, i,i+first-1, end);
else
{
for(j=i; j<=end; ++j)
tmp[j] = num[j];
}
}
void sort(int num[], int first, int end)
{
int n = first+end+1;
int *tmp = (int*)malloc(n*sizeof(int));
int k = 1;
while(k<=n)
{
MergePass(num, tmp, k, n-1);
k = 2*k;
MergePass(tmp, num, k,n-1);
k = 2*k;
}
}
int main()
{
int a[10] = {2,3,1,10,9,7,8,9,2,6};
int n = sizeof(a)/sizeof(a[0]);
sort(a,0,n-1);
for(int i=0; i<n; ++i)
{
cout<<a[i]<<" ";
}
}
快速排序
(1)基本思想:找到一个关键字并把这个关键字放到合适的位置,使关键字的左边的数都比它小,右边的数都比它大,最后递归左边和右边
代码:
int partion(int a[], int low, int high)
{
int key = a[low];
int tmp;
while(low < high)
{
while(high > low && a[high] >= key)
{
--high;
}
tmp = a[low];
a[low] = a[high];
a[high] = tmp;
while(low < high && a[low] <= key)
{
++low;
}
tmp = a[low];
a[low] = a[high];
a[high] = tmp;
}
return low;
}
void sort(int num[], int low, int high)
{
int key;
if(low < high)
{
key = partion(num,low, high);
sort(num, low, key-1);
sort(num, key+1, high);
}
}
int main()
{
int a[10] = {2,3,1,10,9,7,8,9,2,6};
int n = sizeof(a)/sizeof(a[0]);
sort(a,0,n-1);
for(int i=0; i<n; ++i)
{
cout<<a[i]<<" ";
}
}
时间复杂度O(n*logn)
空间复杂度O(logn);
快排优化:
(1)取三个关键字进行排序,将中间数作为枢轴,一般取左中右三个数,比较大小,让关键字等于中间那个数
(2)采用替换而不是交换的方式来进行操作
代码:
void swap(int a, int b)
{
a = a + b;
b = a - b;
a = a - b;
}
int partion(int a[], int low, int high)
{
int mid = (high+low)/2;
if(a[low] > a[high])
{
swap(a[low], a[high]);
}
if(a[mid] > a[low])
{
swap(a[mid], a[low]);
}
if(a[mid] > a[high])
{
swap(a[mid], a[high]);
}
int key = a[low];
int tmp = key;
while(low < high)
{
while(high > low && a[high] >= key)
--high;
a[low] = a[high];
while(low < high && a[low] <= key)
++low;
a[high] = a[low];
}
a[low] = tmp;
return low;
}
void sort(int num[], int low, int high)
{
int key;
while(low < high)
{
key = partion(num, low, high);
sort(num, low, key-1);
low = key+1;
}
}
int main()
{
int a[10] = {2,3,1,10,9,7,8,9,2,6};
int n = sizeof(a)/sizeof(a[0]);
sort(a,0,n-1);
for(int i=0; i<n; ++i)
{
cout<<a[i]<<" ";
}
}
总结篇:
排序方法 | 平均情况 | 最好情况 | 最坏情况 | 辅助空间 | 稳定性 |
冒泡排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 |
简单选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 稳定 |
直接插入排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 |
希尔排序 | O(n*logn)~O(n^2) | O(n^1.3) | O(n^2) | O(1) | 不稳定 |
堆排序 | O(n*logn) | O(n*logn) | O(n*logn) | O(1) | 不稳定 |
归并排序 | O(n*logn) | O(n*logn) | O(n*logn) | O(n) | 稳定 |
快速排序 | O(n*logn) | O(n*logn) | O(n*logn) | O(logn)~O(n) | 不稳定 |
(2)从最坏的情况下:归并和堆排序和快速排序
(3)空间复杂度:归并和快速排序要求的空间大
(4)稳定性:归并排序的稳定性最好
(5)综合:快排是性能最好的方法
代码