排序:就是将一组杂乱无章的数据按照一定的规律组织起来。
按照大类分为稳定排序和不稳定排序。稳定排序就是指两个大小相同的顺序在排序之后顺序不变,而不稳定排序那就不一定了。
快速排序、希尔排序、堆排序、直接选择排序不是稳定的排序算法。
详细图解如下:
//在这里:N(log2N)是指log以2为底N的对数
稳定排序的适用条件:1.本来就有序 2.数组比较短
在这篇博客中会详细讲到七种排序算法,具体的过程在代码中以注释的形式体现:
1.冒泡排序:
两层循环,相邻元素进行比较
void BubbleSort(int array[], int size){
if (size <= 1){//首先判断长度
return;
}
//循环套循环,大循环遍历乱序的数组,小循环在里面进行交换。
//有序区间逐渐变长,无序区间逐渐变短。
int bound = 0;
//[0,bound)当前的有序区间 [bound,size)乱序排序
for (; bound < size; ++bound){
//循环的目的就是找到一个最小的数字
int cur = size - 1;
for (; cur>bound; --cur){
if (array[cur] < array[cur - 1])
//提前定义好交换函数,直接进行调用
Swap(&array[cur], &array[cur-1]);
}
}
return;
}
下面是测试代码以及主函数,经过调试结果无误:
void TestBubbleSort() {
int array[] = { 9, 5, 2, 7 };
BubbleSort(array, sizeof(array) / sizeof(array[0]));
//打印数组元素的函数也是提前写好的,直接调用就可以
PrintArray(array, sizeof(array) / sizeof(array[0]));
}
int main() {
TestBubbleSort();
system("pause");//防闪退
return 0;
}
2.选择排序:
打擂台的思想,和冒泡排序十分相似,都是两层循环,但实际比较不一样
void SelectSort(int arr[], int size){
if (size < 1){
return;
}
//首先搭建出来一个有序区间
int bound = 0;
for (; bound < size; ++bound){
//定义一个最小元素,比它小就交换位置
int cur = bound;
for (; cur < size; ++cur){
if (arr[cur] < arr[bound]){
Swap(&arr[cur], &arr[bound]);
}
}
return;
}
小结:以上两种排序的异同点:
思想:冒泡法是根据大小找位置,而选择法是确定位置来找数
效率:选择法一次移动一个位置,效率慢,而冒泡法时间复杂度太高,同样效率也慢
稳定:选择排序法稳定度不高
3.插入排序:
(线性表)分成有序区间(0,bound)和待排序区间[bound,size},将后面的元素尝试插入有序线性表的合理位置。类似扑克牌洗牌的过程。移动的都是位置,而不是具体的数值。
void InsertSort(int arr[], int size){
if (size < 1){
return;
}
//将bound位置的元素插入到前面线性表的合理位置
int bound = 0;
for (; bound < size; ++bound){
int bound_value = arr[bound];
int i = bound;//定义一个位置
for (; i>0; --i){
//相比之下较大就交换,从小到大
if (arr[i - 1]>bound_value){
arr[i] = arr[i - 1];
//把bound_value放在下标为i的位置上
}
else{
break;//已经找到一个合适的值,跳出循环
}
}
arr[i] = bound_value;//当i等于0时,循环退出
}
}
4.堆排序(升序用大堆、降序用小堆)其重点在于堆的建立和删除
(1)建立大堆(上浮式 从前往后遍历、下沉式 需要从后往前遍历);
(2)循环删除堆顶元素(1、将堆顶根结点和末尾元素进行交换,此时末尾就是最大值,2、重新调整堆结构,再次调整堆顶元素和末尾元素,3、不断重复此步骤,直至满足堆的性质,变得有序)
void AdjustDown(int arr[], int size, int index){
int parent = index;
int child = 2 * parent + 1;//左子树
while (child < size){
if (child + 1 < size&&arr[child + 1] > arr[child]){
//右子树比左子树大,指向右子树
child = child + 1;
}//然后和父节点进行比较
if (arr[child]>arr[parent]){
Swap(&arr[child], &arr[parent]);
}
else{//调整结束
break;
}
parent = child;//作为新的起始节点
child = 2 * parent + 1;
}
return;
}
void HeapCreate(int arr[], int size){
if (size <= 1){
return;
}
//下沉式调整,从最后一个非叶子节点往前遍历
//size-1就是最后一个元素的下标
int i = (size - 1 - 1) / 2;//当前元素的父节点
for (; i >= 0; --i){
AdjustDown(arr, size, i);
}
}
void HeapPop(int arr[], int size){
if (size <= 1){
return;
}
//交换堆顶元素和末尾元素
Swap(&arr[0], &arr[size - 1]);
Down(arr, size - 1, 0);
}
void HeapSort(int arr[], int size) {
if (size <= 1) {
return;
}
// 1. 建立大堆,调用函数
HeapCreate(arr, size);
// 2. 循环删除堆顶元素
// 每次删除一个元素, 就把当前的最大值放到数组末尾了
int i = 0;
for (; i < size; ++i) {
HeapPop(arr, size - i);
}
}
5.希尔(shell)排序:(以人名命名的一种算法),一种改进版本的插入排序
思路:将数据分组,每一组再进行插入排序
Gap步长即是组数,组内排序成功,再依次交替组成一个新的数组(插入排序)
下列情况效果比较好:长度N 步长N/2,N/4,N/8...1
void ShellSort(int arr[], int size){
if (size <= 1){
return;
}
int gap = size / 2;
for (; gap >= 1; gap /= 2){//步长
int bound = gap;
for (; bound < size; bound++){
int bound_value = arr[bound];
int i = bound;
for (; i >= gap; i -= gap) {
// 第三重循环, 负责在当前组中找到一个合适位置,插入并进行搬运
if (arr[i - gap] > bound_value) {
arr[i] = arr[i - gap];
}
else{
break;
}
}
arr[i] = bound_value;
}
}
return;
}
6.归并排序(merger):分治思想,利用递归和迭代算法
面向对象:两个有序的数组
思路:申请一个可以放下两个数组的空间,对于两个数组的开头设定两个指针,比较指针所指向的数字大小,小的直接放入新的数组,指针顺应后移。
7.快排:找到一个基准值
第一个下标从前往后遍历,尝试找到第一个大于基准值的元素,第二个下标从后往前遍历,尝试找到第一个小于基准值的元素,相同就交换位置。