#include<iostream>
#include<vector>
#include<ctime>
using namespace std;
//选择排序,每次选择最小的元素,将它与数组的第一个元素交换位置,再再从数组剩下的元素中选择出最小的元素,将它与第二个元素交换位置。不断进行这样的操作,直到将整个数组排序
//就算有序也要O(n^2)时复
//不稳定
void Select_sort(vector<int>& nums){
//最后一位就不用交换了
for(int i = 0 ; i < nums.size()-1; ++i){
int minn = i;
for(int j = i + 1; j < nums.size(); ++j){
if(nums[j] < nums[minn]){
minn = j;
}
}
swap(nums[i],nums[minn]);
}
}
//冒泡排序,从左到右不断交换相邻逆序的元素,在一轮循环后,可以让未排序的最大元素上浮到右侧,如果在一轮循环中没有交换,说明数组是有序的,可以直接退出
//时间复杂度O(n^2) ,最好O(n)
// 稳定
void Bubble_sort(vector<int>& nums){
bool isSorted;
//如果没有交换就退出
for(int i = nums.size()-1; i > 0 && !isSorted; --i){
//每次要进来重置isSorted
isSorted = true;
//因为要取j+1,所以j要<i
for(int j = 0; j < i; ++j){
//如果后面比当前小,就交换
if(nums[j+1] < nums[j]){
isSorted = false;
swap(nums[j],nums[j+1]);
}
}
}
}
//插入排序,平均O(n^2),最好O(n),最坏O(n^2),每次都将当前元素插入到左侧已经排序的数组中,使得插入之后左侧数组依然有序
//插入排序每次只能交换相邻元素,令逆序对减少1,因为插入排序需要交换的次数为逆序数量
//插入排序的时间复杂度取决于数组的初始顺序,如果数组已经部分有序了,那么逆序较少,需要的交换次数也就较少,时间复杂度较低
//从右边换到左边,左边是有序,只要碰到左边是比自己小的,就不用交换下去了,因为更左边只会更小
//稳定
void Insertion_sort(vector<int>& nums){
for(int i = 1; i < nums.size(); ++i){
for(int j = i; j > 0; --j){
if(nums[j] >= nums[j-1]) break;
swap(nums[j],nums[j-1]);
}
}
}
//希尔排序,插排很慢,因为只能交换相邻的元素,每次只能将逆序数量减少1,希尔排序就是为了解决插排的局限性,通过交换不相邻的元素,每次可以将逆序数量减少大于1
//希尔排序使用插入排序对间隔h的序列进行排序,通过不断减小h,最后令h=1,就可以使得整个数组是有序的
//希尔排序的时间复杂度是O(nlogn)到O(n^2)之间,达不到平方级别,但是另外几种高级排序只会比希尔排序快2倍左右
//不稳定
void Shell_sort(vector<int>& nums){
int h = 1;
while(h < nums.size()/3){
h = 3*h+1;
}
while(h >= 1){
for(int i = h ; i < nums.size(); ++i){
for(int j = i; j >= h; j-=h){
if(nums[j] >= nums[j-h]) break;
swap(nums[j],nums[j-h]);
}
}
h = h/3;
}
}
//归并排序:核心思想是分治,先拆分,自顶向下拆分,每次将这个数组拆分成两半,然后将两个子数组分别拆分,直到拆分到数组大小为1,那么它就是有序的,不用拆分了,然后向上合并,将两个有序的子数组合并为一个有序的子数组
//平均时复,最坏和最好都是O(nlogn),空间复杂度是O(n),向上归并的时候需要一个辅助数组,辅助数组需要一开始就申请号不要每次到内部申请太浪费内存
//稳定
void Merge_sort(vector<int>& nums,int left, int right,vector<int>& fuzhu){
if(left == right) return;
int mid = left + (right-left)/2;
Merge_sort(nums,left,mid,fuzhu);
Merge_sort(nums,mid+1,right,fuzhu);
//到这步已经拆分好了,接下来都是归并了
for(int i = left; i <= right; ++i){
fuzhu[i] = nums[i];
}
int i = left, j = mid+1;
int k = left;
while(i != mid+1 && j != right+1){
if(fuzhu[i] <= fuzhu[j]){
nums[k++] = fuzhu[i++];
}
else{
nums[k++] = fuzhu[j++];
}
}
if(i == mid+1){
while(j != right+1){
nums[k++] = fuzhu[j++];
}
}
else if(j == right+1){
while(i != mid+1){
nums[k++] = fuzhu[i++];
}
}
}
//快速排序:通过一个切分元素将数组分为两个子数组,左子数组小于等于切分元素,右子数组大于等于切分元素,将这两个数组排序也就将整个数组排序了
//切分操作:假设我们取第一个作为切分元素,从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于它的元素,交换这两个元素。不断进行这个过程,就可以保证左指针i的左侧元素都不大于切分元素,右指针j的右侧元素都不小于切分元素。当两个指针相遇时,将a[i]和a[j]交换位置
//原地排序,不需要辅助数组,但是递归调用需要调用栈
//平均时复和最好时复是O(nlogn),最好是每次都对半分,最坏是每次切分的一边都是空的,第一次从最小的切分,第二次从第二小的,这样时复就是O(n^2)了 ,所以在排序前先打乱,不稳定
void my_shuffle(vector<int>& nums){
srand(time(0));
for(int i = nums.size()-1; i >= 0; --i){
int j = rand()%(i+1);
swap(nums[i],nums[j]);
}
}
int Partion(vector<int>& nums, int left,int right){
int v = left;
//因为用的是前置++,所以一开始都设为取不到的值
int i = left, j = right+1;
//从左往右找到第一个比自己大的,当走到最右边就退出
//从右往左找到第一个比自己小的,当走到最左边就退出
//当i和j相遇就退出,不然就swap
while(true){
while(nums[++i] <= nums[v]) if(i == right) break;
while(nums[--j] >= nums[v]) if(j == left) break;
if(i >= j) break;
swap(nums[i],nums[j]);
}
swap(nums[v],nums[j]);
return j;
}
void Quick_sort(vector<int>& nums,int left,int right){
if(left >= right) return;
int partion_Point = Partion(nums,left,right);
Quick_sort(nums,left,partion_Point-1);
Quick_sort(nums,partion_Point+1,right);
}
//堆排序,堆中某个节点的值总是大于等于或小于等于其子结点的值,并且堆是一棵完全二叉树
//不稳定, 三个时复都是O(nlogn),空复是O(1),如果不用现成的优先队列就要手写堆
//假设在数组中的下标从1开始,那么位置为k的结点的父结点的位置为k/2,两个孩子分别是2k和2k+1
class Heap{
public:
Heap(int maxN){
heap.resize(maxN+1);
}
bool isEmpty(){
return heap.empty();
}
int size(){
return heap.size();
}
//插入操作,将元素放到数组末尾,然后上浮到合适的位置
void insert(int v){
heap.push_back(v);
swim(heap.size());//swim的参数是下标
}
//删除最大元素
int deleteMax(){
int maxn = heap[1];
swap(heap[1],heap[heap.size()]);
heap.pop_back();
sink(1);
return maxn;
}
private:
vector<int> heap;
//上浮操作:如果一个节点比父结点大,那么就需要交换两个结点,交换后还可能比它的父结点大,因此需要不断地进行比较和交换操作
void swim(int k){
while(k > 1 && heap[k/2] < heap[k]){
swap(heap[k/2],heap[k]);
k /= 2;
}
}
//下沉操作,如果一个节点比子结点小,需要不断地向下进行比较和交换操作,一个节点如果有两个子结点,应该和两个结点中最大的那个结点交换
void sink(int k){
while(2*k <= heap.size()){
int j = 2*k;
if(j < heap.size() && heap[j] < heap[j+1]){
j++;
}
if(heap[k] >= heap[j]) break;
swap(heap[k],heap[j]);
k = j;
}
}
};
//堆排序不用自己实现堆,可以想想堆是如何实现的就想的起来堆排序怎么手撕了
//堆排序的数组是从1开始的 ,只要写一个下沉操作就行了
void sink(vector<int>& nums, int k, int n){
while(2*k <= n){
int j = 2*k;
if(j < n && nums[j] < nums[j+1]){
j++;
}
if(nums[k] >= nums[j]) break;
swap(nums[k],nums[j]);
k = j;
}
}
void Heap_sort(vector<int>& nums){
//如果你一开始数组数组下标从0开始,你需要在最前面插入一个东西才行,这样你要排序的是后面的
nums.insert(nums.begin(),0);
int n = nums.size()-1;
for(int k = n/2; k >= 1; --k){
sink(nums,k,n);
}
while(n > 1){
swap(nums[1],nums[n--]);
sink(nums,1,n);
}
}
int main(){
vector<int> nums = {
3,2,1};
//Select_sort(nums);
//Bubble_sort(nums);
//Insertion_sort(nums);
//Shell_sort(nums);
//vector<int> fuzhu(nums.size());
//Merge_sort(nums,0,nums.size()-1,fuzhu);
//my_shuffle(nums);
//Quick_sort(nums,0,nums.size()-1);
Heap_sort(nums);
for(int i = 1; i < nums.size(); ++i){
cout<<nums[i];
}
return 0;
}
手写七大排序以及注释(C++)
猜你喜欢
转载自blog.csdn.net/J_avaSmallWhite/article/details/114437508
今日推荐
周排行