测试用例:
交换操作需要三次赋值,而移动操作只需要一次赋值!
1.冒泡排序
算法原理:(冒泡排序就是把小的元素往前调或者把大的元素往后调<把值比较大的沉到底或把值较小的浮到顶>。比较是相邻的两个元素比较,交换也发生在这两个元素之间)
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
时间复杂度:冒泡排序总的平均时间复杂度为O(n^2)
算法代码:
void bubbleSort(int a[], int n)
{
int i, j, temp;
for (i=0; i<n-1; i++)
{
for(j=0; j<n-i-1; j++) //j<(n-1)-i
{
if (a[j] > a[j+1])
{
temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
}
void bubbleSort(int* a, int n) {
bool exchange = true;
for(int i=0; i<n-1 && exchange; ++i) {
exchange = false;
for(int j=0; j<n-1-i; ++j) {
if(a[j]>a[j+1]) {
exchange = true;
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
}
void bubble_sort(int a[], int n)
{ // 稳定的排序
// 交换标志exchanged,我们希望用这个标志减少不必要的扫描.
// 当它为真时,表明交换之前数组无序,但我们也不能确保在交换之后数组每一个
// 元素都排到有序状态下的正确位置了,所以再对数组进行扫描是必要的.
// 当它为假时,表明数组有序了,不必再对数组进行扫描了.
bool exchange = true; // 算法开始前,自然假设数组无序
for( int i = n - 1; i > 0 && exchange; --i ) { // 最多做n-1趟扫描
exchange = false; // 在一趟扫描开始前,我们总假设这趟扫描是不必要的
for( int j = 0; j < i; ++j ) { // 对当前无序区a[0:i]进行扫描
if( a[j+1] < a[j] ) {
// 大的往下沉,而小的往上冒
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
exchange = true; // 发生了交换,故将交换标志置为真
}
}
}
}
2.快速排序
算法原理:快速排序采用的思想是分治思想
快速排序是找出一个元素(理论上可以随便找一个)作为基准(pivot),然后对数组进行分区操作,使基准左边元素的值都不大于基准值,基准右边的元素值 都不小于基准值,如此作为基准的元素调整到排序后的正确位置。递归快速排序,将其他n-1个元素也调整到排序后的正确位置。最后每个元素都是在排序后的正 确位置,排序完成。所以快速排序算法的核心算法是分区操作,即如何调整基准的位置以及调整返回基准的最终位置以便分治递归。
时间复杂度:平均时间复杂度为O(nlgn)
算法代码:
void quickSort(int a[], int left, int right)
{
if(left < right) {
int pivot = a[left];
int low = left;
int high = right;
while(low < high) {
while(low < high && a[high] >= pivot) {
high--;
}
if (low < high) {
a[low++] = a[high];
}
while(low < high && a[low] <= pivot) {
low++;
}
if (low < high) {
a[high--] = a[low];
}
}
a[low] = pivot;
quickSort(a, left, low-1);
quickSort(a, low+1, right);
}
}
3.直接插入排序
算法原理:每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子序列中的适当位置,直到全部记录插入完成为止。
设数组为a[0…n-1]。
- 初始时,a[0]自成1个有序区,无序区为a[1..n-1]。令i=1
- 将a[i]并入当前的有序区a[0…i-1]中形成a[0…i]的有序区间。
- i++并重复第二步直到i==n-1。排序完成。
时间复杂度:直接插入排序属于稳定的排序,最坏时间复杂度为O(n^2),空间复杂度为O(1)。
算法代码:
void insertSort(int a[], int n)
{
int i, j, temp;
for (i=1; i<n; ++i) {
temp = a[i];
//为a[i]在前面的a[0...i-1]有序区间中找一个合适的位置
for (j=i-1; j>=0 && a[j]>temp; --j) {//将比a[i]大的数据向后移
a[j+1] = a[j];
}
a[j+1] = temp;//将a[i]放到正确位置上
}
}
执行结果:
Before sorting: 4 1 5 6 2 7 3
1 sorting: 1 4 5 6 2 7 3
2 sorting: 1 4 5 6 2 7 3
3 sorting: 1 4 5 6 2 7 3
4 sorting: 1 2 4 5 6 7 3
5 sorting: 1 2 4 5 6 7 3
6 sorting: 1 2 3 4 5 6 7
After sorting: 1 2 3 4 5 6 7
4.shell排序
算法原理:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
时间复杂度:XX排序
算法代码:
void shellSort(int *a, int n)
{
int i,j,gap,temp;
for (gap=n/2; gap>0; gap/=2) {
for (i = gap; i < n; ++i) {
temp = a[i];
for(j=i-gap; j>=0&&a[j]>temp; j-=gap) {
a[j+gap] = a[j];
}
a[j+gap] = temp;
}
}
}
通常会把主要排序核心算法封装为shellPass,然后增量根据情况定义:
void shellPass(int*a, int d, int n) {
int i,j,temp;
for(i=d; i<n; ++i) {
if (a[i] < a[i-d]) {
temp = a[i];
for(j=i-d; j>=0 && a[j]>temp; j-=d) {
a[j+d] = a[j];
}
a[j+d] = temp;
}
}
}
void shellSort(int* a, int n) {
int d = n;
do {
d = d/3+1;
shellPass(a,d, n);
}while(d>1);
}
void shellSort2(int* a, int n) {
for(int gap=n/2; gap>0; gap/=2) {
shellPass(a, gap, n);
}
}
5.选择排序
算法原理:工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。 选择排序是不稳定的排序方法(比如序列[5, 5, 3]第一次就将第一个[5]与[3]交换,导致第一个5挪动到第二个5后面)
n个记录的文件的直接选择排序可经过n-1趟直接选择排序得到有序结果:
①初始状态:无序区为R[1..n],有序区为空。
②第1趟排序
在无序区R[1..n]中选出关键字最小的记录R[k],将它与无序区的第1个记录R[1]交换,使R[1..1]和R[2..n]分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。
……
③第i趟排序
第i趟排序开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。
时间复杂度:O(n^2)
选择排序的交换操作介于 0 和 (n - 1) 次之间。选择排序的比较操作为 n (n - 1) / 2 次之间
算法代码:
void selectSort(int* a, int n) {
int i,j,min;
for(i=0; i<n-1; ++i) {
min=i;
for(j=i+1; j<n; ++j) {
if(a[j]<a[min]) {
min = j;
}
}
if(min!=i) {
int temp = a[i];
a[i] = a[min];
a[min] = temp;
}
}
}
//from small to big sort(if change a[j]<a[min] to a[j]>a[min], then sort from big to small)
6.归并排序
算法原理:
归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
归并过程为:比较a[i]和a[j]的大小,若a[i]≤a[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;否则将第二个有序表中的元素a[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。归并排序的算法我们通常用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。
归并操作的工作原理如下:
第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置
第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
重复步骤3直到某一指针超出序列尾
将另一序列剩下的所有元素直接复制到合并序列尾
时间复杂度:
归并排序的最好、最坏和平均时间复杂度都是O(nlogn),而空间复杂度是O(n),比较次数介于(nlogn)/2和(nlogn)-n+1,赋值操作的次数是(2nlogn)。因此可以看出,归并排序算法比较占用内存,但却是效率高且稳定的排序算法。
(速度仅次于快速排序,为稳定排序算法,一般用于对总体无序,但是各子项相对有序的数列)
算法代码:
void merge(int *a, int *temp, int start, int mid, int end) {
print(a, start, end);
int i=start;
int j=mid+1;
int k=start;
while(i<=mid && j<=end) {
if(a[i] < a[j]) {
temp[k++] = a[i++];
} else {
temp[k++] = a[j++];
}
}
while(i<=mid) {
temp[k++] = a[i++];
}
while(j<=end) {
temp[k++] = a[j++];
}
for(i=start; i<k; ++i) {
a[i] = temp[i];
}
}
void mergeSort(int *a, int *temp, int start, int end) {
if(start < end) {
int mid = (start+end)/2;
mergeSort(a, temp, start, mid);
mergeSort(a, temp, mid+1, end);
merge(a, temp, start, mid, end);
}
}
7.堆排序
算法原理:
堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。
(1)用大根堆排序的基本思想
① 先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区
② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key
③由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。
……
直到无序区只有一个元素为止。
时间复杂度:O(n*logn)
由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。
堆排序是就地排序,辅助空间为O(1).
它是不稳定的排序方法
算法代码:
// a是待调整的堆数组,i是待调整的数组元素的位置,n是数组的长度
// 本函数功能是:根据数组a构建大根堆
void HeapAdjust(int a[], int i, int n)
{
// 子结点的位置=2*(父结点位置)+1,nChild为左孩子,nChild<n即为判断左孩子是否小于数组长度
// 如果左孩子不小于数组长度,即该结点没有左右孩子,为叶子结点,不需要调整
for(int nChild=2*i+1; nChild<n; i=nChild) {
// 得到子结点中较大的结点
// nChild为左孩子,nChild<n-1即为nChild+1<n,nChild+1即为右孩子, 意思为判断右孩子是否小于数组长度
// 也就是说判断此结点是否存在右孩子, 如存在,判断左右孩子中较大者,取得较大者的index
if(nChild<n-1 && a[nChild+1]>a[nChild]) {
++nChild;
}
// 如果较大的子结点大于父结点那么把较大的子结点往上移动,替换它的父结点
if(a[i] < a[nChild]) {
int nTemp=a[i];
a[i]=a[nChild];
a[nChild]=nTemp;
} else {
// 父结点比左右孩子结点值都大,无须调整,退出即可
break;
}
}
}
//堆排序算法
void HeapSort(int a[],int n)
{
int i;
// 调整序列的前半部分元素,调整完之后第一个元素是序列的最大的元素
// n/2-1是最后一个非叶节点,此处"/"为整除
for(i=n/2-1; i>=0; --i) {
HeapAdjust(a, i, n);
}
// 从最后一个元素开始对序列进行调整,不断的缩小调整的范围直到第一个元素
for(i=n-1; i>0; --i) {
// 把第一个元素和当前的最后一个元素交换,
// 保证当前的最后一个位置的元素都是在现在的这个序列之中最大的
a[i]=a[0]^a[i];
a[0]=a[0]^a[i];
a[i]=a[0]^a[i];
// 不断缩小调整heap的范围,每一次调整完毕保证第一个元素是当前序列的最大值
HeapAdjust(a, 0, i);
}
}
8.XX排序
算法原理:原理内容
时间复杂度:XX排序
算法代码:
{
}
9.XX排序
算法原理:原理内容
时间复杂度:XX排序
算法代码:
{
}
10.XX排序
算法原理:原理内容
时间复杂度:XX排序
算法代码:
{
}
参考链接:
http://zh.wikipedia.org/wiki/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95
http://blog.csdn.net/touch_2011/article/details/6767673
http://student.zjzk.cn/course_ware/data_structure/web/paixu/paixu8.1.1.1.htm
http://blog.csdn.net/morewindows/article/details/6665714