目录
1.排序的概念及其应用
1.1排序的概念
排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
1.2排序的应用
莲花楼是真好看
1.3常见的排序算法及其接口
// 排序实现的接口 // 插入排序 void InsertSort(int* a, int n); // 希尔排序 void ShellSort(int* a, int n); // 选择排序 void SelectSort(int* a, int n); // 堆排序 void AdjustDwon(int* a, int n, int parent); void HeapSort(int* a, int n); // 冒泡排序 void BubbleSort(int* a, int n) // 快速排序递归实现 // 快速排序hoare版本 int PartSort1(int* a, int left, int right); void QuickSort(int* a, int left, int right); // 归并排序递归实现 void MergeSort(int* a, int n) // 计数排序 void CountSort(int* a, int n)
2.常见排序算法的实现(升序)
2.1插入排序
2.1.1直接插入排序
基本思想:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为 止,得到一个新的有序序列 。
举一个简单的例子,我们逢年过节打扑克的时候,从抓第一张牌开始,我们每抓一张都依次和前面的比较,直到找到合适的位置插入进去
下面我们一起来看代码
void InsertSort(int* a, int n) { for (int i = 0; i < n-1; i++) { int end = i;//一开始只有一个数 int tmp = a[i+1];//保存下一个数 while (end >= 0)//下一个数和前面的数比较 { if (a[end] > tmp) { a[end + 1] = a[end]; --end; } else { break; //遇到不大于的数时停止 } } a[end + 1] = tmp;//插入数据 } }
我们发现当一个数组是逆序时,直接插入排序第一次交换1次,第二次交换2次,第n-1次交换n-1次
时间复杂为O(n^2),那么当数组是逆序时,我们有什么办法改进插入排序吗,下面让我们介绍希尔排序
2.1.2希尔排序
希尔排序法的基本思想是:(先进行预排序)先选定一个整数gap,把待排序文件中所有记录分成gap个 组,所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后,重复上述分组和排序的工 作。当到达gap=1时,所有记录在统一组内排好序。
gap的取法有很多种,最初Shell提出gap取【n/2】,gap=【gap/2】直到gap=1;
后来Knuth提出取gap=【gap/3】+1;也有人说各gap互质为好。无论哪一种主张都没有得到证明
void ShellSort(int* a, int n) { int gap = n; while (gap > 1) { gap = gap / 3 + 1; //多组并排 for (int i = 0; i < n - gap; ++i) { int end = i; int tmp = a[end + gap]; while (end >= 0) { if (a[end] > tmp) { a[end + gap] = a[end]; end -= gap; } else { break; } } a[end + gap] = tmp; } } }
希尔排序总结:1. 希尔排序是对直接插入排序的优化。 2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就 会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。 3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的 希尔排序的时间复杂度都不固定
2.2选择排序
2.2.1直接选择排序
基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。
具体步骤:遍历数组,找最小放在左边,最大放在右边,思想比较简单,现实中用的比较少,直接上代码
void SelectSort(int* a, int n) { int end = n - 1; int begin = 0; while (begin < end) { int maxi = begin; int mini = begin; for (int i = begin; i <= end; i++)//遍历数组,找最大和最小 { if (a[i] > a[maxi]) { maxi = i; } if (a[i] < a[mini]) { mini = i; } } swap(a[begin], a[mini]);//将最大和最小放在两边 if (begin == maxi)//注意交换的时候,如果最大或最小在两边,交换的时候会改变位置 { maxi = mini; } swap(a[end], a[maxi]); --end; ++begin; } }
2.2.2堆排序
基本思想:堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
步骤:交换堆顶元素与最后一个元素,再调整建堆,直到有序。
代码如下:
void AdjustDown(int *a,int n,int parent) { int child = parent * 2 + 1; while (child < n) { if (child + 1 < n && a[child] < a[child + 1]) { child = child + 1; } if (a[parent] < a[child]) { swap(a[parent], a[child]); parent = child; child = parent * 2 + 1; } else { break; } } } void HeapSort(int* a, int n) { for (int i = (n - 2) / 2; i >= 0; i--) { AdjustDown(a, n, i); } int end = n - 1; while (end > 0) { swap(a[0], a[end]); AdjustDown(a, end, 0); end--; } }
2.3交换排序
2.3.1冒泡排序
基本思想:依次与后面的值作比较,比后面的值大则交换,直到最大的值排在最后,再排次大的值,冒泡排序是一种很容易理解的排序。
void BubbleSort(int* a, int n)
{
for (int j = 0; j < n - 1; j++)
{
int ret = 0;
for (int i = 0; i < n - 1 -j; i++)
{
if (a[i] > a[i + 1])
{
swap(a[i], a[i + 1]);
ret = 1;
}
}
if (ret == 0)//如果没有发生交换,说明数组有序
{
break;
}
}
}
2.3.2快速排序
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
对左边和右边进行相同步骤,完成排序。
//快速排序hoare版本 int PartSort1(int* a, int left, int right) { int middle = GetMiddle(a, left, right); swap(a[left], a[middle]); int keyi = left; while (left < right) { //右边先走,一定停在小的地方 while (left < right && a[right] >= a[keyi]) { right--; } while (left < right && a[left] <= a[keyi]) { left++; } swap(a[left], a[right]); } swap(a[keyi], a[left]); return left; } void QuickSort(int* a, int begin, int end) { if (begin >= end) { return; } int keyi = PartSort1(a, begin, end); QuickSort(a, begin, keyi - 1); QuickSort(a, keyi + 1,end); }
2.4归并排序/计数排序
2.4.1归并排序
基本思想: 归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:
归并排序类似于二叉树的后序遍历,先对左区间归并,再对右区间归并,最后左右再归并
void _MergeSort(int *a,int begin,int end,int *tmp) { if (begin == end) return; //小区间优化,减少递归次数 if (end - begin + 1 < 10) { InsertSort(a + begin, end - begin + 1); return; } int middle = (begin + end) / 2; _MergeSort(a, begin, middle, tmp); _MergeSort(a, middle + 1, end, tmp); int begin1 = begin; int begin2 = middle+1; int end1 = middle; int end2 = end; int i = begin; while (begin1 <= end1 && begin2 <= end2) { if (a[begin1] < a[begin2]) { tmp[i++] = a[begin1++]; } else { tmp[i++] = a[begin2++]; } } while (begin1 <= end1) { tmp[i++] = a[begin1++]; } while (begin2 <= end2) { tmp[i++] = a[begin2++]; } memcpy(a + begin, tmp + begin, sizeof(int)*(end - begin + 1));//memcpy头文件<string.h> } void MergeSort(int* a, int n) { int* tmp = (int *)malloc(sizeof(int) * n); _MergeSort(a, 0, n - 1, tmp); free(tmp); }
2.4.2计数排序
思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。
操作步骤: 1. 统计相同元素出现次数 2. 根据统计的结果将序列回收到原来的序列中
void CountSort(int* a, int n) { int min = a[0], max = a[0]; for (int i = 0; i < n; i++) { if (a[i] < min) { min = a[i]; } if (a[i] > max) { max = a[i]; } } int range = max - min + 1; int* countA = (int*)malloc(sizeof(int) * range); memset(countA, 0, sizeof(int) * range); // 统计次数 for (int i = 0; i < n; i++) { countA[a[i] - min]++; } // 排序 int k = 0; for (int j = 0; j < range; j++) { while (countA[j]--) { a[k++] = j + min; } } }
3.排序算法复杂度及稳定性分析
稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次 序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
感谢您对博主文章的关注与支持!在阅读本篇文章时,我们希望您能留下宝贵的意见,如有错误,欢迎大家随时指正~
如果本篇文章对您起到一定帮助,期待各位分享与转发,这将为博主提供巨大的支持与鼓励!