一、低级排序
1. 插入排序(低级排序中速度最快的方法)
类似于打扑克牌时自己拿牌:首先摸到第一张牌,那么默认是有序的,接着第二张牌过来,那就将其与其前的第一张牌进行比较,如果大于,那就放在第二个位置;如果小于,那就将第一张牌向后移一个位置,空出第一个位置,给这张新牌。当在来一张新牌的时候,我们就在进行类似于之前的比较工作,如果小于,就挪位置,大于就放后边。直至拿到所有的牌。
#include <iostream>
using namespace std;
void sortNum(int* a,int num) //因为是直接传入指针,所以不需要返回值,指针指向的内容也会改变
{
int temp;
// N 层循环做完整个队列的事
for(int i = 1;i < num ;i++)
{
//一层循环做一件完整的事:比较大小、交换并挪出空位
for(int j = i - 1;a[j] > a[j + 1] && j >= 0;j--)
{
temp = a[j + 1];
a[j + 1] = a[j];
a[j] = temp;
}
}
}
int main()
{
const int num = 9;
int a[num] = {5,4,6,3,2,1,7,3,5};
sortNum(a,num);
for(int i = 0;i < num;i++)
{
cout<<a[i];
}
return 0;
}
但是,有的时候我们不能确定我们使用的类型,所有我们会使用C++提供的模板来简化多个不同类型,相同操作的函数:
#include <iostream>
using namespace std;
template <class T>
/*函数模板在编译的时候会自动进行实例化,创建不同的函数出来*/
void sortNum(T* a,int num) //因为是直接传入指针,所以不需要返回值,指针指向的内容也会改变
{
T temp;
// N 层循环做完整个队列的事
for(int i = 1;i < num ;i++)
{
//一层循环做一件完整的事:比较大小、交换并挪出空位
for(int j = i - 1;a[j] > a[j + 1] && j >= 0;j--)
{
temp = a[j + 1];
a[j + 1] = a[j];
a[j] = temp;
}
}
}
int main()
{
const int num = 9;
int a[num] = {5,4,6,3,2,1,7,3,5};
float b[num] = {5.3,4.0,6.2,3.1,2.7,1.5,7.3,3.1,5.5};
sortNum(a,num);
sortNum(b,num);
for(int i = 0;i < num;i++)
{
cout<<a[i]<<"\t";
}
cout<<endl;
for(int i = 0;i < num;i++)
{
cout<<b[i]<<"\t";
}
return 0;
}
2. 冒泡排序
冒泡排序的思想就类似于煮开水你晓得,在一趟循环里,我们会对比临近的两个元素,然后通过比大小、换位置,再进行下一个元素。而且很有意思的是,最大的泡泡(最大数)往往会先沉底,所以我们在循环的时候,可以控制循环的趟数来对代码进行优化。
#include <iostream>
using namespace std;
template <class T>
void sortNum(T* a,int num)
{
for(int j = 0;j < num - 1;j++)
{
for(int i = 0;i < num - 1;i++)
{
if(a[i] > a[i + 1])
{
T temp = a[i + 1];
a[i + 1] = a[i];
a[i] = temp;
}
}
}
}
int main()
{
const int num = 9;
int a[num] = {5,4,6,3,2,1,7,3,5};
float b[num] = {5.3,4.0,6.2,3.1,2.7,1.5,7.3,3.1,5.5};
sortNum(a,num);
sortNum(b,num);
for(int i = 0;i < num;i++)
{
cout<<a[i]<<"\t";
}
cout<<endl;
for(int i = 0;i < num;i++)
{
cout<<b[i]<<"\t";
}
return 0;
}
优化代码:
#include <iostream>
using namespace std;
template <class T>
void sortNum(T* a,int num)
{
for(int j = 0;j < num - 1;j++)
{
for(int i = 0;i < num - 1 -j;i++)
{
if(a[i] > a[i + 1])
{
T temp = a[i + 1];
a[i + 1] = a[i];
a[i] = temp;
}
}
}
}
int main()
{
const int num = 9;
int a[num] = {9,4,6,3,2,1,7,3,5};
float b[num] = {5.3,4.0,6.2,3.1,2.7,1.5,7.3,3.1,5.5};
sortNum(a,num);
sortNum(b,num);
for(int i = 0;i < num;i++)
{
cout<<a[i]<<"\t";
}
cout<<endl;
for(int i = 0;i < num;i++)
{
cout<<b[i]<<"\t";
}
return 0;
}
3. 选择排序
选择排序与冒泡排序类似,但是也有自己的特点,它每次都会从当前未排序的整数中找一个最小的数,将它放在已排序的整数链表的最后。而且选择排序是要比冒泡排序速度快的,因为冒泡排序要经常性的进行交换,而选择排序选出最小的时候,并不会进行交换,而是在最后的位置进行一次交换,所以花的时间就更少一些。
#include <iostream>
using namespace std;
template <class T>
void sortNum(T* a,int num)
{
for(int j = 0;j < num;j++)
{
int flag = j;
for(int i = j + 1;i < num;i++)
{
if(a[flag] > a[i])
{
flag = i;
}
}
T temp = a[flag];
a[flag] = a[j];
a[j] = temp;
}
}
int main()
{
const int num = 9;
int a[num] = {9,4,6,3,2,1,7,3,5};
float b[num] = {5.3,4.0,6.2,3.1,2.7,1.5,7.3,3.1,5.5};
sortNum(a,num);
sortNum(b,num);
for(int i = 0;i < num;i++)
{
cout<<a[i]<<"\t";
}
cout<<endl;
for(int i = 0;i < num;i++)
{
cout<<b[i]<<"\t";
}
return 0;
}
二、高级排序
1. 快速排序
最流行的排序算法,速度最快的排序算法。递归方法实现。
pivot:枢轴、枢纽
看网上大多将递归形式,我还是先用迭代来搞,我觉得比递归好理解,嗯,我不管(✿◡‿◡)
#include <iostream>
using namespace std;
void quickSort(int* a,int num)
{
int i,j;
for(int pivot = num - 1;pivot > 0;pivot /= 2) //进行多次循环,选取新的枢轴排序
{
for(int t = 0;t < num;t++) //进行一次循环,将一个枢轴为基准的数进行排序
{
for(i = 0;a[i] < a[pivot];i++) //定位下一个需要交换的大数脚标
{
;
}
for(j = num - 1;a[j] > a[pivot];j--) //定位下一个需要交换的小数脚标
{
;
}
int temp = a[j]; //将找出的大小数进行交换
a[j] = a[i];
a[i] = temp;
}
}
}
int main()
{
int num = 7;
int a[num] = {7,6,8,1,6,9,2};
quickSort(a,num);
for(int i = 0;i < num;i++)
{
cout<<a[i]<<"\t";
}
return 0;
}
然后来想一下递归形式,我觉得写递归的前提是你知道一次完整的事情是什么,然后其他的事情重复下来是不是流程相同,如果相同就可以写成递归形式,let's do it:
#include <iostream>
using namespace std;
void quickSort(int* a,int end,int pivot)
{
for(int t = 0;t <= end;t++)
{
int i,j;
for(int t = 0;t <= end / 2;t++)
{
for(i = 0;a[i] < a[pivot];i++)
{
;
}
for(j = end;a[j] > a[pivot];j--)
{
;
}
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
pivot /= 2;
if(pivot > 0)
{
quickSort(a,end,pivot);
}
}
}
int main()
{
int num = 7;
int a[num] = {7,6,8,1,6,9,2};
quickSort(a,num - 1,num - 1);
for(int i = 0;i < num;i++)
{
cout<<a[i]<<"\t";
}
return 0;
}
自己写的代码还是有点丑呀,慢慢练习吧,多敲多学习嘛,也罗列一个别写的代码:
分的比我要彻底一些,而且,但总体思想是一样的。
2. 归并排序
- 归并
- 迭代归并
仍旧按照自己的理解,写一个循环版的排序方案:
#include <iostream>
using namespace std;
void mergeSort(int* a,int num)
{
for(int t = 2;t < num;t++) //不同步长的划分,然后分别进行不同组别的排序
{
for(int j = 0;j < num / t;j++) //第二层循环是将下面的每个小组进行排序
{
for(int i = j * t;i< (j+1)* t;i++) //第一层循环是在一个小组内部进行排序
{
if(a[i] > a[i+1])
{
int temp = a[i+1];
a[i+1] = a[i];
a[i] = temp;
}
}
}
}
}
int main()
{
int num = 7;
int a[num] = {7,6,8,1,6,9,2};
mergeSort(a,num);
for(int i = 0;i < num;i++)
{
cout<<a[i]<<"\t";
}
return 0;
}
然后继续提出递归算法:
#include <iostream>
using namespace std;
void mergeSort(int* a,int t,int num)
{
if(t < num)
{
for(int j = 0;j < num / t;j++)
{
for(int i = j * t;i < (j + 1) * t;i++)
{
if(a[i] > a[i+1])
{
int temp = a[i+1];
a[i+1] = a[i];
a[i] = temp;
}
}
}
t++;
mergeSort(a,t,num);
}
}
int main()
{
int num = 9;
int a[num] = {7,6,8,1,6,9,2,55,3};
mergeSort(a,2,num);
for(int i = 0;i < num;i++)
{
cout<<a[i]<<"\t";
}
return 0;
}
再学习一哈别人的算法:大佬会重新创建一个数组,然后排序,但是在我看来,这是没有必要的空间开销。。。嗯。。。
这是写这篇博客的不知多少天后,因为发现其实之前对归并排序的核心还是有误解,所以在书写代码的时候有些地方有问题,这也就是为什么我的代码效率不高的原因,于是从新写了归并:
#include <iostream>
using namespace std;
void mmerge(int* arr,int L,int R)
{
int M = (L + R) / 2 + 1;
int LEFT_SIZE = M - L;
int RIGHT_SIZE = R - M + 1;
int left[LEFT_SIZE];
int right[RIGHT_SIZE];
int k = L;
for(int i = 0;i < LEFT_SIZE;i++,k++)
{
left[i] = arr[k];
}
for(int i = 0;i < RIGHT_SIZE;i++,k++)
{
right[i] = arr[k];
}
k = L;
int i,j;
for(i = 0,j = 0;i < LEFT_SIZE && j < RIGHT_SIZE;k++)
{
if(left[i] <= right[j])
{
arr[k] = left[i];
i++;
}
else
{
arr[k] = right[j];
j++;
}
}
while(j < RIGHT_SIZE)
{
arr[k] = right[j];
j++;
k++;
}
while(i < LEFT_SIZE)
{
arr[k] = left[i];
i++;
k++;
}
}
void mergesort(int* arr,int L,int R)
{
//递归停止条件:分治法使得当前管理的数组元素拆分成最小的单元即一个数据大小
if(L == R)
{
return ;
}
else
{
int M = (L + R) / 2 + 1;
mergesort(arr,L,M - 1);
mergesort(arr,M,R);
mmerge(arr,L,R);
}
}
int main()
{
int num = 8;
int arr[num] = {3,7,8,9,4,6,9,10};
mergesort(arr,0,num - 1);
for(int t = 0;t < num;t++)
{
cout<<arr[t]<<endl;
}
return 0;
}
3. 基数排序
基数:十进制的基数是10,二进制的基数是2。如果对扑克牌花色排序就是4进制,如果对1-K排序,就是13进制。
示例:
一般分为两种:MSD:先排最高位; LSD:先排最低位。以LSD为例:
以个位为准:将数字按个位排序,放入对应的链表里,然后再将链表里的数据取出来,按序放入数组中
以十位为准(图上错了,哈哈哈):将数字按十位排序,放入对应的链表里,然后再将链表里的数据取出来,按序放入数组中
以百位为准(图上错了,哈哈哈):将数字按百位排序,放入对应的链表里,然后再将链表里的数据取出来,按序放入数组中
4. 堆排序
表格版
排序方法 | 时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 | 复杂性 |
---|---|---|---|---|---|---|
直接插入排序 | O(n^2) | O(n2) | O(n) | O(1) | 稳定 | 简单 |
希尔排序 | O(nlog2n) | O(n^2) | O(n) | O(1) | 不稳定 | 较复杂 |
直接选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 | 简单 |
堆排序 | O(nlog2n) | O(nlog2n) | O(nlog2n) | O(1) | 不稳定 | 较复杂 |
冒泡排序 | O(n^2) | O(n2) | O(n) | O(1) | 稳定 | 简单 |
快速排序 | O(nlog2n) | O(n2) | O(nlog2n) | O(nlog2n) | 不稳定 | 较复杂 |
归并排序 | O(nlog2n) | O(nlog2n) | O(nlog2n) | O(n) | 稳定 | 较复杂 |
基数排序 | O(d(n+r)) | O(d(n+r)) | O(d(n+r)) | O(n+r) | 稳定 | 较复杂 |
三、查找算法
1. 顺序查找
如果当前查找数组无序,只能使用顺序查找。如果有序,使用折半(二分)查找,或顺序查找均可。顺序查找很慢,100万个数据,平均要查找50万次。
#include <iostream>
using namespace std;
bool sequentialSearch(int* a,int num,const int x)
{
bool find = false;
for(int i = 0;i < num;i++)
{
if(a[i] == x)
{
find = true;
}
}
return find;
}
int main()
{
const int num = 9;
int a[num] = {9,4,6,3,2,1,7,3,5};
int x;
cin>>x;
bool find = sequentialSearch(a,num,x);
cout.setf(ios_base::boolalpha);
cout<<find<<endl;
return 0;
}
2. 折半查找(二分查找)
使用前提:必须查找序列式有序的。
#include <iostream>
using namespace std;
template <class T>
void sortNum(T* a,int num)
{
for(int j = 0;j < num;j++)
{
int flag = j;
for(int i = j + 1;i < num;i++)
{
if(a[flag] > a[i])
{
flag = i;
}
}
T temp = a[flag];
a[flag] = a[j];
a[j] = temp;
}
}
bool binarySearch(int* a,int num,const int x)
{
bool find = false;
for(int i = 0;i != (num - 1);)
{
int flag = (i+num)/2;
if(x > a[flag])
{
i = flag+1;
}
else if(x == a[flag])
{
find = true;
return find;
}
else
{
num = flag - 1;
}
}
return find;
}
int main()
{
const int num = 9;
int a[num] = {9,4,6,3,2,1,7,3,5};
sortNum(a,num);
for(int i = 0;i < num;i++)
{
cout<<a[i]<<"\t";
}
cout<<endl;
int x;
cin>>x;
bool find = binarySearch(a,num,x);
cout.setf(ios_base::boolalpha);
cout<<find<<endl;
return 0;
}
使用迭代方法实现二分查找:
#include <iostream>
using namespace std;
bool binarySearch(int* a,int start,int end,int x)
{
bool find = false;
if(start != end)
{
int flag = (start + end) / 2;
if(x < a[flag])
{
find = binarySearch(a,start,flag - 1,x);
}
else if(x > a[flag])
{
find = binarySearch(a,flag + 1,end,x);
}
else
{
find = true;
}
}
return find;
}
int main()
{
const int num = 14;
int a[num] = {0,1,3,4,5,7,9,10,12,17,22,23,46,78};
int x;
cin>>x;
cout.setf(ios_base::boolalpha);
cout<<binarySearch(a,0,num-1,x);
return 0;
}
四、递归
递归是神,迭代是人。这节建议去b站上上个课,哈哈哈,有个很棒的讲解视频,地址链接:https://www.bilibili.com/video/av31763085/?p=6
递归其实实质上就是循环,将中间过程得不到的就暂存起来,直到找到出口,然后将过程慢慢返回,逐层返回。
递归占用内存大,需要不断的调用函数,会占用大量的内存,但是其书写更容易理解。
#include <iostream>
using namespace std;
int jieCheng(int num)
{
if(num == 0)
{
return 1;
}
else
{
num = jieCheng(num-1) * num;
}
}
int main()
{
int num;
cin>>num;
cout<<jieCheng(num)<<endl;
return 0;
}
如果用普通循环实现:
#include <iostream>
using namespace std;
int jieCheng(int num)
{
int sum = 1;
for(int i = num;i > 0;i--)
{
sum *= i;
}
return sum;
}
int main()
{
int num;
cin>>num;
cout<<jieCheng(num)<<endl;
return 0;
}