个人理解记录————别从冒泡开始~
直接上快排
参考引用:
快速排序
关键在于理解整个流程,简单来说就是在每一次的循环中两个标志位的不断逼近和交换。C语言网这里抄的是这样描述:
首先在数组中选择一个基准点,然后分别从数组的两端扫描数组,设两个指示标志(low指向起始位置,high指向末尾),首先从后半部分开始,如果发现有元素比该基准点的值小,就交换low和high位置的值,然后从前半部分开始扫描,发现有元素大于基准点的值,就交换low和high位置的值,如此往复循环,直到low>=high,然后把基准点的值放到high这个位置。
字太多了,下面这个博主的博文足以描述核心的说法。
https://blog.csdn.net/morewindows/article/details/6684558
核心:挖坑填数+分治法
每一次的流程是一个挖坑填数,整体是分而治之。
挖坑填数:
一个数组,当我们取出基数的时候,相当于数据被挖了一个坑,我们一般取第一个数为基数。
有坑就要填,怎么填?
从后开始找数填坑同时形成新坑,按照如下规则找:
- 从后往前时,找小的基数的
- 从前往后时,找大于基数的
- 一后一前依次寻找直到前后标志相遇
所以每一次基数相同的时候都是一个循环,在这个循环离会分别从后往前和从前往后搜索全数组,直到i==j
OK,看一百遍不如自己写一遍,试试
//先构建一个大循环,表示在这个循环的时候,基数是固定的x,i和j是起点和终点,一开始基数可以设置为第一个数
x =a[i]
while (i<j)
{
//在循环内部,这里巧妙的点在于,我们不需要知道具体怎么交换,一共交换了多少次,只需要设置好边界条件即可
//先后向前,这里先找到合适的j,这里又判断了一次i<j是为什么
/* 外层那个表示初始基数的大小情况,这里是自减的边界
找到小于基数的数的位置,可以用for带break
for (;j>i;j--)
{
if (a[j]<=x)
{
a[i] = a[j];
i++;
break;
}
}
*/
while(i<j && a[j]>x)
j--;
//防止j--到最后也没找到相关值,相当于a[i]i之后就是最小值
if (i<j)
{
a[i] = a[j];
i++;
/*不++也行,++就是避免重复运算,a[i]=a[j]<=x,i这边要找的是>x的,填a[j]的坑
不手动++的话,a[i]=x,就死循环了*/
}
//重复上述操作,这里的i<j不能省略,因为上面的ij可能已经相等了
while(i<j && a[i]<x)
i++;
//防止j--到最后也没找到相关值,相当于a[i]i之后就是最小值
if (i<j)
{
a[j] = a[i];
j--;
/*不++也行,++就是避免重复运算,a[i]=a[j]<=x,i这边要找的是>x的,填a[j]的坑
不手动++的话,a[i]=x,就死循环了*/
}
}
//循环结束,必有i==j
a[i] = x;
return i;
挖坑填数的部分OK,那分而治之呢,用递归
得到中间数之后,两边再依次挖坑填数,直到只剩一个数
if (start<end)
{
mid = digArray();//mid这个数可以不用管了实际上
quiksort(a,start,mid-1);
quiksort(a,mid+1,end);
}
上面这个是用递归的写法,能用递归就能用循环。怎么做?先放着
归并排序
跟快排差不多,我觉得可以用:归并+分而治之 当作核心思想
归并类似于一次往单管道里填充填数,啥意思?就好像有一根管子,我们总是取一个比较小的数字放进去,直到管道被填满。嗯,比快排好懂
贴一下概念:归并排序(Merge sort)是建立在归并操作上的一种有效、稳定的排序算法,该算法是采用分治法(Divide and
Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
还是写一下就懂
#include <iostream>
using namespace std;
//单次的归并操作,感觉比较好理解,唯一需要注意的就是边界条件要缕清,这里是<=,用<的话start2=mid?
void mergeSortArray(int a[], int first, int mid, int last, int temp[])
{
//这里比较需要注意的是边界条件
int start1 = first;
int end1 = mid;
int start2 = mid+1;
int end2 = last;
int k = 0;
while (start1 <= end1 && satrt2 <= end2)
{
if (a[start1] <= a[start2])
temp[k++] = a[start1++];
else
temp[k++] = a[start2++];
}
while (start1 <= end1)
{
temp[k++] = a[start1++];
}
while (start2 <= end2)
{
temp[k++] = a[start2++];
}
//注意下面这个函数的写法
for(int i=0;i<k;i++)
{
a[i+first] = temp[i];
}
}
void mergeSort(int a[], int first, int last, int temp[])
{
//这里依然唯一需要注意的是边界条件,传进来的last=n-1,和上面<=有关
if (first < last)
{
int mid = (first+last)/2;
mergeSort(a,first,mid,temp);
mergeSort(a,mid+1,last,temp);
mergeSortArray(a, first, mid, last, temp);
}
}
void main()
{
int a[9] = {3,8,6,5,5,4,1,7,2};
int n = 9;
int* temp = new int[9];
mergeSort(a,0,n-1,temp);
for (int i=0;i<n;i++)
cout<<a[i]<<' ';
delete [] temp;
}
同样的,能用递归就能用循环。
这里直接看这个博主写的,很好很清晰。
https://blog.csdn.net/dong132697/article/details/132646066
归并是怎么变成循环的?我们用递归排序的时候,是不断对半分,直到分成只有一个数的时候。那当我们拿到一个数组,其中的每个数都可以看作是递归到最下层的情况。于是,我们可以把1 2 3 4 5 6 7 8 位次分别递归,而后就是12 34 56 78,最后演变为1234 5678。
依据我们做事的核心思想:从简到难,就先把最简单的情况写出来,就是刚好有2^n是可以完美划分的。那么
mergeSort2(int a[], int first, int last, int temp)
{
int gap = 1; //gap就是每一次划分组的时候的每组里的数量
int i = 0;
int k = 0; //归并必备辅助数组的指针
while(gap<n) //完美情况下,gap=n,不需要再归并了
{
//每一次归并就是一个循环,我们每次都从0开始,其实就是一个依次划分的游戏,间2gap一个新的划分
//除了上面这些,每次实际间隔划分就是一次归并,那就和上面的归并没有区别,把3个while拿过来
//那唯一剩下的问题,start1/2,end1/2是什么
for (i = 0;i<n;i += 2*gap)
{
start1 = i;//每一次循环都是从i开始的
end1 = i+gap-1;//2gap对半分,为什么减一,因为下面<=会达到end1
start1 = i+gap;
end2 = i+2*gap;
/* 核心关键,k需要每次更新,这是不同于递归的地方,递归每次进到函数,k都是0
递归完成,实际使用的就是数组前面的一小段,所以数组a和temp不能整体交换,只交换前面k位
*/
k = i;
while (start1 <= end1 && satrt2 <= end2)
{
if (a[start1] <= a[start2])
temp[k++] = a[start1++];
else
temp[k++] = a[start2++];
}
while (start1 <= end1)
temp[k++] = a[start1++];
while (start2 <= end2)
temp[k++] = a[start2++];
}
swap(a,temp);//每次循环完成,temp数组就是最终数组,具有连贯性
gap = 2*gap;
}
}
OK,有了完美情况,再考虑特殊边界条件,这里还是看上面参考的博主的文章,这里不再赘述,那在我们的代码里怎么写这个特殊边界?
说白了,还是改start1/2,end1/2
#define min(a, b) (a<b?a:b)
start1 = i;//不需要改变
end1 = min(n-1,i+gap-1); //特殊情况下,end1就是数组的末尾n-1
start2 = i+gap; //两种特殊情况,先考虑简单的,存在,则为i+gap,另一种情况,实际上不存在,那么end2也应该不存在,但是不能都设置为n-1,不然会导致start2=end2,也会做一次归并操作
end2 = min(n-1,i+gap*2); //这里要加min,让end有所限制,避免不存在时start2=end2,存在时=min(n-1,i+gap*2)
good,归并的两种形式都拿下,对其原理也比之前理解的更加深刻了。
回到最上面,快速排序的循环形式怎么写?
快速排序(循环写法)
有一丢复杂,先鸽了