三个简单排序,原理和代码实现都非常简单,后边会奉上进阶版本的排序。本文中只以不降序排序举例。
1.冒泡排序
算法思想:每次将待排序列中最大的数放置在末尾,然后将减小待排序列长度,循环n-1次,当待排序列长度为1时,完成排序。算法平均复杂度O(),空间复杂度O(1),是稳定排序。下边的代码在交换的时候,是用异或进行交换,可以不使用额外空间来交换两个数,不过发现仿佛速度有点慢,大家知道怎么回事就行了,面试的时候可能会被问到。
#include<bits/stdc++.h>
using namespace std;
int main() {
int n ;
cin>>n;
int R[n];
for(int i = 0; i < n; i++) {
cin>>R[i];
}
//bubble sort
for(int i = 0; i < n-1; i++) {
for(int j = 0; j < n-1-i; j++) {
if(R[j] > R[j+1]) {
R[j] = R[j] ^ R[j+1];
R[j+1] = R[j] ^ R[j+1];
R[j] = R[j] ^ R[j+1];
}
}
}
//print
for(int w = 0; w < n; w++) {
cout<<R[w]<<" ";
}
cout<<endl;
return 0;
}
2.直接选择排序
算法思想:每次找到序列中最小的值。依次与序列前边的值交换,循环n-1次,排序完成。平均时间复杂度复杂度O(),空间复杂度O(1),是不稳定排序。选择排序应该是最直接最容易理解的一种算法了。
#include<bits/stdc++.h>
using namespace std;
int main() {
int n;
cin>>n;
int R[n];
for(int i = 0; i < n; i++) {
cin>>R[i];
}
//select sort
for(int i = 0; i < n-1; i++) {
for(int j = i; j < n; j++) {
if(R[j] < R[i]) {
R[j] = R[j] ^ R[i];
R[i] = R[j] ^ R[i];
R[j] = R[j] ^ R[i];
}
}
}
//print
for(int w = 0; w < n; w++) {
cout<<R[w]<<" ";
}
cout<<endl;
return 0;
}
3.直接插入排序
算法思想:顾名思义就是把数字按规则插入到指定位置上,看到其他博客用扑克牌来说明感觉很形象,即手里拿的牌是有序的,每次将新来的牌插入在正确的位置。平均时间复杂度O(),空间复杂度O(1),是稳定排序。
#include<bits/stdc++.h>
using namespace std;
int main() {
int n;
cin>>n;
int R[n];
for(int i = 0; i < n; i++) {
cin>>R[i];
}
//insert sort
for(int i = 1; i < n; i++) {
int j = i;
int temp = R[j];
while(j > 0 && R[j] >= temp)
{
R[j]=R[j-1];
j--;
}
R[j]=temp;
}
//print
for(int w = 0; w < n; w++) {
cout<<R[w]<<" ";
}
cout<<endl;
return 0;
}
这三个算法是最简单的排序算法,但也至关重要,后续更加先进快速的排序大多是在同样的思想上衍生出来的。
接下来是由以上三个简单排序算法进阶而来的三个高级排序算法:
4.希尔排序--直接插入排序进阶版
算法思想:将整个待排序列分成若干份子序列,对每份子序列分别使用插入排序,当对最后一份子序列(即增量因子为1的时候)使用插入排序后,整个序列即有序。希尔排序的时间复杂度很难定论,这里不深入了。希尔排序是不稳定算法。
网上找到一张图,我觉得不错,可惜不知道怎么的将第二次分组后的排序过程忽略了,还是放上来帮助大家理解,

丢失的第二步,大同小异,将每隔两位的数字一起看成一个子序列,然后使用直接插入排序。这样进行两次插入排序,两个子序列就有序了,就可以去第三步了。
看到这里,你肯定不知所以然,难道直接插入排序它不香吗,为什么要这么繁琐。所谓的希尔排序竟然要这么多次直接插入排序后才能完成排序,为什么会比直插要快呢?
当我们使用直接插入排序的时候,如果碰到数组末尾的数字与数组开头的数字位置摆放错误,那按照直插的思想,需要将中间的所有数字后移,造成效率低下,如果在一次排序中碰到很多很多个这样的情况,那时间浪费的就太多了。
希尔排序做的就是先将整体序列逐渐向有序的方向逼近。在希尔排序开始的时候,因为dk(用来分子序列的间隔)很大,所以分成的子序列会很小,虽然可能会分很多子序列,但是这个时候用直插的效率非常的高(因为短啊),随着dk的渐渐缩小,整体序列也变的越来越有序,对于整体基本有序的数组,用直插的效率也很高。
分而治之的思想又体现出来了。。。
希尔排序代码:
#include<bits/stdc++.h>
using namespace std;
const int num = 10;
void insertsort(int R[],int cnt,int dk)
{
for(int i = dk;i<cnt;i++)
{
if(R[i]<R[i-dk])//如果后边的数字比前边的小 就要对当前数字在当前序列插入排序
{
int j = i - dk;
int x = R[i];
while(j>=0&&x<R[j])
{
R[j+dk] = R[j];
j -= dk;
}
R[j+dk] = x;
}
}
return ;
}
void shellsort(int R[],int cnt)
{
int div = 2;
int dk = cnt/div;
while(dk>=1)
{
insertsort(R,10,dk);
dk/=div;
}
return ;
}
int main()
{
int R[num] = {49,38,65,97,76,13,27,49,55,04};
shellsort(R,num);
for(int i = 0;i<num;i++)
{
cout<<R[i]<<" "<<endl;
}
return 0;
}
5.堆排序--直接选择排序进阶版
算法思想:要掌握堆排序,得先了解什么是堆。所谓堆,其实就是完全二叉树的形式。堆又分为大根堆和小根堆,大根堆顾名思义,即在二叉树中,根节点是最大的,每一个根节点都大于自己的左右孩子,小根堆则恰好相反,第一次接触这个概念的同学直接看图好理解点。


那堆排序就是将我们的待排序列转换成这样的堆。那转换好后干嘛呢?
因为此时堆的堆顶一定满足是当前堆的最大或最小值(取决与是大根堆还是小根堆,本文后边统一默认用大根堆举例),那我们就把这个堆顶的最大值放在待排数组中的最后一位,然后不管它了,将它从二叉树中移除,在我们的堆中,用最后一个叶子节点来顶替他的位置。
这个时候我们的堆就不满足大根堆的性质了,经过一系列的操作,我们把他重新转换为大根堆,这时的堆顶又是当前二叉树中的最大值,再将堆顶放在待排数组中的倒数第二位。循环往复,直到二叉树被删没,我们的数组就有序啦!
下边以大根堆举例:

了解了整体过程后,我们可以看到,堆排序主要由两个步骤组成:
1.建立初始大根堆。
2.拿走并替换根节点,重新维护大根堆。
首先从建立初始大根堆来说,将待排数组按顺序列为一颗完全二叉树,然后从他的最后一个非叶子节点开始操作(完全二叉树共n个节点,则第一个非叶子节点为n/2 ),比较它和儿子节点的大小,符合性质就去判断倒数第二个非叶子节点,不符合就和较大的儿子交换位置,然后再判断转换位置后还符不符合,不符合继续交换,直至符合为止,然后去判断倒数第二个非叶子节点。直到操作到根节点后,初始大根堆就建立好了。
再说维护大根堆,之前的大根堆是完好的,然而用新元素将堆顶替换掉了而导致不符合大根堆性质,所以需要维护使之符合。由于只是因为栈顶的元素变化,那我们在维护的时候只要把这个小东西摆到应该摆的位置上,大根堆就维护好了。过程和建立初始大根堆类似,不同的是,维护大根堆直接从根节点开始操作,将根节点摆放在符合的位置上,那大根堆就已经维护好了,不需要去判断别的。
堆排序代码:
#include<bits/stdc++.h>
using namespace std;
//大顶堆
void HeapAdjust(int R[],int pos,int n) {
int temp = R[pos];
for(int i=2*pos; i<=n; i*=2) {
if(i<n&&R[i]<R[i+1])
i++;//取儿子中较大的
if(R[pos]>R[i])
break;//当前小堆满足大顶堆性质
else {
R[pos] = R[i];
pos = i;
}
R[pos] = temp;
}
return ;
}
void HeapSort(int R[],int n) {
//构建初始大顶堆
for(int i = n/2; i>0; i--) {
HeapAdjust(R,i,n);
}
for(int i = n; i>0; i--) {
int temp = R[1];
R[1] = R[i];
R[i] = temp;
HeapAdjust(R,1,i-1);
}
return ;
}
void Print(int R[]) {
for(int i=1; i<sizeof(R); i++) {
printf("%d\n",R[i]);
}
return ;
}
int main() {
int n = 8;
//数组中的下标为0的位置不可用,随便给个数字滥竽充数,哈哈
int R[n+1] = {99999,99,87,67,32,56,23,9,11};
//堆排序
HeapSort(R,n);
Print(R);
return 0;
}
5426

被折叠的 条评论
为什么被折叠?



