文章目录


稳定:冒泡,插入,归并
交换排序
冒泡排序
依次比较相邻两元素,若前一元素大于后一元素则交换之,直至最后一个元素即为最大;然后重新从首元素开始重复同样的操作,直至倒数第二个元素即为次大元素;依次类推。如同水中的气泡,依次将最大或最小元素气泡浮出水面。
#include <iostream>
#include <algorithm>
#include<vector>
using namespace std;
void bubblesort(vector<int> &v)
{
//若flag为true说明有过数据交换,否则停止循环
bool flag=true;
for(int i=0;i<v.size() && flag;i++)
{
flag=false;
for(int j=v.size()-2;j>=i;j--)
{
if(v[j]>v[j+1])
{
swap(v[j],v[j+1]);
flag=true;
}
}
}
}
int main() {
vector<int> v{3,44,35,5,47,15,36,26,27,2,46,4,19,50,48};
bubblesort(v);
for(auto x:v)
cout<<x<<' ';
return 0;
}
快速排序
快速排序的思想:任取一个数据元素作为基准,做一次划分,讲所有数据元素集合分成3部分;中间部分值包含一个基准元素,它的前面包含所有关键字小于基准的数据元素,它的后面包含所有关键字大于基础的数据元素,然后分别对前后两部分进行划分。
对数据元素 v 1 v n v_1~v_n v1 vn做一趟划分,不失一般性,选择第一个元素作为基准元素,用t代表基准。设i=1,j=n,划分过程如下:
- 1.比较基准元素与第j个元素 a j a_j aj,如果 a j a_j aj>t,则j- -;继续步骤1;如果 a j < t a_j<t aj<t,则将 a j a_j aj移到i的位置,i=i+1转步骤2。
- 2.比较基准元素与第i个元素 a i a_i ai,如果 a j a_j aj<t,则i++;继续步骤2;如果 a i > t a_i>t ai>t,则将 a i a_i ai移到j的位置,j=j+1转步骤3。
- 3.重复步骤1和2,直到i=j,将基准元素放到i的位置。
快排优化:
- 3 种快排基准选择方法:
随机(rand函数)、固定(队首、队尾)、三数取中(队首、队中和队尾的中间数)
- 4种优化方式:
优化1:当待排序序列的长度分割到一定大小后,使用插入排序
优化2:在一次分割结束后,可以把与Key相等的元素聚在一起,继续下次分割时,不用再对与key相等元素分割
优化3:优化递归操作
优化4:使用并行或多线程处理子序列
#include <iostream>
#include <algorithm>
#include<vector>
using namespace std;
int Partition(vector<int> &v,int low ,int high)
{
int pivot=v[low];//用子表的第一个记录作枢轴记录
while(low<high)
{
while(low<high && v[high]>=pivot)
high--;//找出第一个比pivot小
v[low]=v[high];//high指示的值小于pivot,交换
while(low<high && v[low]<=pivot)
low++;//找出第一个比pivot大
v[high]=v[low];//low指示的值大于pivot,交换
}
v[low]=pivot;
return low;
}
void quickSort(vector<int>&v,int low,int high)
{
if(low<high)
{
int i=Partition(v, low, high);
quickSort(v, low, i-1);
quickSort(v, i+1, high);
}
}
int main() {
vector<int> v{3,44,35,5,47,15,36,26,27,2,46,4,19,50,48};
int length=v.size();
quickSort(v,0,length);
for(auto x:v)
cout<<x<<' ';
return 0;
}
选择排序
简单选择排序
#include <iostream>
#include<algorithm>
#include<vector>
using namespace std;
void SelectSort(vector<int> &v)
{
int min_index;
for(int i=0;i<v.size();i++)
{
min_index=i;
for(int j=i+1;j<v.size();j++)
{
if(v[min_index]>v[j])//如果有小于当前最小值的关键字
min_index=j;//将此关键字的下标赋值给min_index
}
if(min_index!=i)
swap(v[min_index],v[i]);
}
}
int main() {
vector<int> v={3,44,35,5,47,15,36,26,27,2,46,4,19,50,48};
SelectSort(v);
for(auto x:v)
cout<<x<<' ';
return 0;
}
堆排序
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆
再简单总结下堆排序的基本思路:
a.将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
#include <stdio.h>
void display(int array[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", array[i]);
}
printf("\n");
}
void swap(int array[], int x, int y) {
int key = array[x];
array[x] = array[y];
array[y] = key;
}
// 从大到小排序
// void Down(int array[], int i, int n) {
// int child = 2 * i + 1;
// int key = array[i];
// while (child < n) {
// if (child + 1 < n && array[child] > array[child + 1]) {
// child++;
// }
// if (key > array[child]) {
// swap(array, i, child);
// i = child;
// } else {
// break;
// }
// child = child * 2 + 1;
// }
// }
// 从小到大排序
void Down(int array[], int i, int n) { // 最后结果就是大顶堆
int parent = i; // 父节点下标
int child = 2 * i + 1; // 子节点下标
while (child < n) {
if (child + 1 < n && array[child] < array[child + 1]) { // 判断子节点那个大,大的与父节点比较
child++;
}
if (array[parent] < array[child]) { // 判断父节点是否小于子节点
swap(array, parent, child); // 交换父节点和子节点
parent = child; // 子节点下标 赋给 父节点下标
}
child = child * 2 + 1; // 换行,比较下面的父节点和子节点
}
}
void BuildHeap(int array[], int size) {
for (int i = size / 2 - 1; i >= 0; i--) { // 倒数第二排开始, 创建大顶堆,必须从下往上比较
Down(array, i, size); // 否则有的不符合大顶堆定义
}
}
void HeapSort(int array[], int size) {
printf("初始化数组:");
BuildHeap(array, size); // 初始化堆
display(array, size); // 查看初始化结果
for (int i = size - 1; i > 0; i--) {
swap(array, 0, i); // 交换顶点和第 i 个数据
// 因为只有array[0]改变,其它都符合大顶堆的定义,所以可以从上往下重新建立
Down(array, 0, i); // 重新建立大顶堆
printf("排序的数组:");
display(array, size);
}
}
int main() {
int array[] = {49, 38, 65, 97, 76, 13, 27, 49, 10};
int size = sizeof(array) / sizeof(int);
// 打印数据
printf("%d \n", size);
printf("排序前数组:");
display(array, size);
HeapSort(array, size);
return 0;
}
#include<iostream>
#include<vector>
using namespace std;
void swap(vector<int>& arr, int a,int b){
arr[a]=arr[a]^arr[b];
arr[b]=arr[a]^arr[b];
arr[a]=arr[a]^arr[b];
}
void adjust(vector<int>& arr,int len,int index){
int maxid=index;
// 计算左右子节点的下标 left=2*i+1 right=2*i+2 parent=(i-1)/2
int left=2*index+1,right=2*index+2;
// 寻找当前以index为根的子树中最大/最小的元素的下标
if(left<len and arr[left]<arr[maxid]) maxid=left;
if(right<len and arr[right]<arr[maxid]) maxid=right;
// 进行交换,记得要递归进行adjust,传入的index是maxid
if(maxid!=index){
swap(arr,maxid,index);
adjust(arr,len,maxid);
}
}
void heapsort(vector<int>&arr,int len){
// 初次构建堆,i要从最后一个非叶子节点开始,所以是(len-1-1)/2,0这个位置要加等号
for(int i=(len-1-1)/2;i>=0;i--){
adjust(arr,len,i);
}
// 从最后一个元素的下标开始往前遍历,每次将堆顶元素交换至当前位置,并且缩小长度(i为长度),从0处开始adjust
for(int i=len-1;i>0;i--){
swap(arr,0,i);
adjust(arr,i,0);// 注意每次adjust是从根往下调整,所以这里index是0!
}
}
int main(){
vector<int> arr={3,4,2,1,5,8,7,6};
cout<<"before: "<<endl;
for(int item:arr) cout<<item<<" ";
cout<<endl;
heapsort(arr,arr.size());
cout<<"after: "<<endl;
for(int item:arr)cout<<item<<" ";
cout<<endl;
return 0;
}
插入排序
直接插入排序
数列前面部分看为有序,依次将后面的无序数列元素插入到前面的有序数列中,初始状态有序数列仅有一个元素,即首元素。在将无序数列元素插入有序数列的过程中,采用了逆序遍历有序数列,相较于顺序遍历会稍显繁琐,但当数列本身已近排序状态效率会更高。
#include <iostream>
#include <algorithm>
#include<vector>
using namespace std;
void InsertSort(vector<int> &v)
{
int i,j,temp;
for(i=1;i<v.size();i++)
{
temp=v[i];
for(j=i-1;j>=0 && temp<v[j];j--)
v[j+1]=v[j];//后移一位
v[j+1]=temp;
}
}
int main() {
vector<int> v{3,44,35,5,47,15,36,26,27,2,46,4,19,50,48};
InsertSort(v);
for(auto x:v)
cout<<x<<' ';
return 0;
}
希尔排序
希尔排序的基本思想如下:选定一个增量
h
1
(
h
1
<
n
)
h_1 (h_1<n)
h1(h1<n),把序列中的数据元素从第1个开始分组,所有相距
h
1
h_1
h1的数据元素分为一组,一共有
h
1
h_1
h1组, 并在各组内采用直接插入提序,使各组内的数据元素排好序,然后选第个增量
h
2
(
h
2
<
h
1
)
h_2(h_2<h_1)
h2(h2<h1),以
h
2
h_2
h2为距离对数据元素进行分组及排序;重复该分组及排序过程,知道增量为1。
希尔排序的性能取决于增量序列的选择。希尔提出取
h
2
=
n
/
2
,
h
i
−
1
=
[
h
i
2
]
h_2=n/2,h_{i-1}=[\frac{h_i}{2}]
h2=n/2,hi−1=[2hi]。Knuth提出取
h
2
=
n
/
2
,
h
i
−
1
=
[
h
i
3
]
h_2=n/2,h_{i-1}=[\frac{h_i}{3}]
h2=n/2,hi−1=[3hi]。
#include <iostream>
#include <algorithm>
#include<vector>
using namespace std;
void ShellSort(vector<int> &v)
{
int increment=v.size()/2;
while(increment)
{
for(int i=increment;i<v.size();i+=1)//对各子表做直接插入排序
{
int temp=v[i], j;
for(j=i-increment;j>=0 && temp<v[j];j-=increment)
{
v[j+increment]=v[j];
}
v[j+increment]=temp;
}
increment/=2;
}
}
int main() {
vector<int> v{3,44,35,5,47,15,36,26,27,2,46,4,19,50,48};
ShellSort(v);
for(auto x:v)
cout<<x<<' ';
return 0;
}
归并排序
- 归并排序 ( Merging Sort) 就是利用归并的思想实现的排序方法。原理是假设初始序列含有 n 个记录 , 则可以看成是 n 个有序的子序列,每个子序列的长度为1 ,然后两两归并,得到[[n/2] ( [x]表示不小于 x 的最小整数)个长度为 2或1的有序子序列;再两两归并 ,…,如此重复 , 直至得到 一个长度为 n 的有序序列为止 ,这种排序方法称为2路归并排序 。
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
void mergeSort(vector<int> &q, int l, int r){
if(l >= r)
return;
int mid = l + r >> 1;
mergeSort(q, l, mid);
mergeSort(q, mid + 1, r);
static vector<int> w;
w.clear();
int i = l, j = mid + 1;
while(i <= mid && j <= r){
if(q[i] > q[j])
w.push_back(q[j++]);
else
w.push_back(q[i++]);
}
while(i <= mid)
w.push_back(q[i++]);
while(j <= mid)
w.push_back(q[j++]);
for(int i : w)
q[l++] = i;
}
int main(){
vector<int> v{3,44,35,5,47,15,36,26,27,2,46,4,19,50,48};
int n=v.size();
mergeSort(v, 0, n - 1);
for(auto x : v)
cout << x << ' ';
return 0;
}
基数排序
#include <iostream>
#include <vector>
using namespace std;
int get(int x, int i){
while(i--)
x /= 10;
return x % 10;
}
void radixSort(vector<int> &q, int n){
vector<vector<int>> cnt(10);
for(int i = 0; i < 3; i++){
for(int j = 0; j < 10; j++)
cnt[j].clear();
for(int j = 0; j < n; j++)
cnt[get(q[j], i)].push_back(q[j]);
for(int j = 0, k = 0; j < 10; j++){
for(int x : cnt[j])
q[k++] = x;
}
}
}
int main(){
vector<int> v{3,44,35,5,47,15,36,26,27,2,46,4,19,50,48};
int n=v.size();
radixSort(v, n);
for(auto x : v)
cout << x << ' ';
return 0;
}