前言
经典的排序和查找方法是每个人都要掌握的,为了更加深刻的记忆它们,我把排序方法记录在这条菜鸟走向大神的罗马大道上。希望这篇文章可以帮到更多的人。
这篇文章的初步分为稳定排序
和不稳定排序
两个部分,稳定又代表了什么呢,下面有一个数组:
arr[ 5
, 5, 2, 1, 3],里面有两个数值相等的5,我们加一个符号标记不同位置的5。
经过稳定排序后:arr[1, 2, 3, 5
, 5] 发现他们的相对位置不改变;
而经过不稳定排序后(如快速排序)数组就变成了arr[1, 2, 3, 5, 5
], 相同数字位置发生改变了。
稳定排序
插入排序
插入排序的核心思想就是保证数组的头部有序,然后向后推进,直到遍历完整个数组。(下面所有代码都是默认从小到大排序)
不失一般性,我们假设数组的前 j - 1个元素已经有序,到了第 j 个元素,我们需要将它放到前 j 个位置适合它的位置, 每次跟它前面的元素比较,小于前面的元素则交换位置
,否则就是目前位置
对于每一个元素 j,最好的情况交换0次,最坏的话交换 j 次。
void insert(int *num, int n) {
for (int i = 1; i < n; i++) {
for (int j = i; j > 0 && num[j] < num[j - 1]; j--) {
swap(num[j], num[j - 1]);
}
}
return ;
}
冒泡排序
冒泡排序和插入排序相反,他是保证数组后面有序。第一步是找到最大的数放在最后一为,循环n遍,每次找到未排序区间里面的最大数
,放在已排序区间的前面。实现即大于右边的进行交换
对于冒泡排序,这里有一个加速的方法,防止有序的数组再遍历一遍。即使用一个变量 times 记录每次遍历交换的次数,如果某一次没有进行交换,则代表数组已经完全有序,可以直接跳出循环。
void bubble_sort(int *num, int n) {
int times = 1;
for (int i = 1; i < n && times; i++) {
times = 0; // 如果有一次循环没有进行交换,证明已经有序了
for (int j = 0; j < n - i; j++) {
if (num[j] > num[j + 1]) {
swap(num[j], num[j + 1]);
times++;
}
}
}
return ;
}
归并排序
归并排序的思想也很简单,就是分治与合并
,将一个数组一分为二
,先把左边的进行排序,然后把右边的进行排序(分治),最后将两个有序的数组合并
到一个数组中再拷贝
到原数组里面。分别对两个小数组进行排序并不需要真正意义上的排序,我们利用递归将它分治成极小的数组(只有一个或两个元素)再将有序的数组合并即可。
void merge_sort(int *num, int l, int r) {
if (r - l <= 1) {
if (r - l == 1 && num[l] > num[r]) {
swap(num[l], num[r]); // 只剩两个元素的时候
}
return;
}
int mid = (l + r) >> 1;
merge_sort(num, l, mid); // 递归对左侧数组进行归并排序
merge_sort(num, mid + 1, r); // 递归对右侧数组进行归并排序
int *temp = (int *)malloc(sizeof(int) * (r - l + 1)); // 有序的数组进行合并
int p1 = l, p2 = mid + 1, k = 0;
while(p1 <= mid || p2 <= r) {
if (p2 > r || (p1 <= mid && num[p1] < num[p2])) { //从左侧数组取值
temp[k++] = num[p1++];
} else {
temp[k++] = num[p2++];
}
}
memcpy(num + l, temp, sizeof(int) * (r - l + 1));
free(temp);
return ;
}
不稳定排序
选择排序
每次选择未排序区间最小值(最大值),然后和下一个应该排序的位置的元素交换位置。
void select_sort(int *num, int n) {
for (int i = 0; i < n - 1; i++) {
int ind = i;
for (int j = i + 1; j < n; j++) {
if (num[ind] > num[j]) ind = j;
}
swap(num[i], num[ind]); // 和应该排序的位置i的元素交换位置
}
return ;
}
快速排序
快速排序的思想就是找一个基准值
,把小于基准值的所有数放在基准值左侧,大于基准值的所有数放在右侧,递归到两个小数组直到所有都有序
void quick_sort(int *num, int l, int r) {
while (l < r) { // 终止条件,对整个大的分组全部完成快排
int x = l, y = r, temp = num[(l + r) >> 1]; //基准值
do {
while (x <= y && num[x] < temp) x++; // 找到左侧第一个大于基准值的数字
while (x <= y && num[y] > temp) y--;// 找到右侧第一个小于基准值的数字
if (x <= y) {
swap(num[x], num[y]); // 交换位置
x++, y--;
}
} while (x <= y); // 基准值右侧全是大于它的数,第一次快排结束
quick_sort(num, x, r); // 对右侧的区间递归块排
r = y; // 对左区间进行块排(此时 l != r)
}
return ;
}
一更到此结束,下次更新各个排序算法之间的复杂度问题
昨天晚上看到一个很精彩的博客,是关于优先队列的,虽然之前会优先队列,但是还是受益匪浅。文章最后的一段话也很有感触,在此写下来了,另外附上博客的链接,需要的兄弟们可以进取看一看。
C++中优先队列priority_queue的基础用法
“人在比较中奋进,同在比较中消亡,
起初面临差距时会奋起直追,但是当努力过后发现距离反而越来越远时,
便会麻木懈怠,曾经的努力没有用吗?
我觉得不是,努力过不一定会成功,但是努力的过程已经印在了骨子里,
这本身就是生活的一部分。
你可以选择这条艰苦的路,同样也可以选择跳过,至于跳过时错失了什么,谁又知道呢?
毕竟人生无法再来过,重新读档只发生在游戏世界中~”