手写七大排序以及注释(C++)

#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;
} 

猜你喜欢

转载自blog.csdn.net/J_avaSmallWhite/article/details/114437508