常用排序算法总结

“从0开始做LeetCode”之常用排序算法总结

1.冒泡排序

Note:先排最大的

时间复杂度: O ( n 2 ) O(n^2) O(n2) 空间复杂度: O ( 1 ) O(1) O(1)

void bubble_sort(vector<int> &nums, int n){	//这里可以传入n,也可以不用
	if(nums.empty() || nums.size()<2){
		return;
	}
	for(int end = nums.size()- 1; end > 0; end--){
		for(int i = 0; i < end; i++){
			if(nums[i] > nums[i+1]){
				swap(nums, i, i+1);
			}
		}
	}
}

void swap(vector<int> &nums, int i, int j){
	int temp = nums[i];
	nums[i] = nums[j];
	nums[j] = temp;
}

2.选择排序

Note:先排最小的

时间复杂度: O ( n 2 ) O(n^2) O(n2) 空间复杂度: O ( 1 ) O(1) O(1)

void selection_sort(vector<int> &nums, int n){
	if(nums.empty() || nums.size() < 2){
		return;
	}
	for(int i = 0; i<nums.size()-1; i++){
		int minIndex = i;
		for(int j = i + 1; j < nums.size() - 1; j++){
			//minIndex = nums[j] < nums[minIndex] ? j : minIndex;
			if(nums[j] < nums[minIndex]){
				minIndex = j;
			}
		}
		swap(nums, i, minIndex);
	}
}

void swap(vector<int> &nums, int i, int j){
	int temp = nums[i];
	nums[i] = nums[j];
	nums[j] = temp;
}

3.插入排序

Note:先排前面的,有点类似插牌

时间复杂度: O ( n 2 ) O(n^2) O(n2) 空间复杂度: O ( 1 ) O(1) O(1)

时间复杂度按照最差情况排序

和数据状态有关系,故:
最好: O ( n ) O(n) O(n)
最坏: O ( n 2 ) O(n^2) O(n2)

平均:

void insertion_sort(vector<int> &nums, int n){	//这里可以传入n,也可以不用
	if(nums.empty() || nums.size()<2){
		return;
	}
	for(int i = 1; i < nums.size(); i++){
		for(int j = i - 1; j >= 0 && nums[j] > nums[j+1]; j--){
		swap(nums, j, j + 1);
		}
	}
}

void swap(vector<int> &nums, int i, int j){
	int temp = nums[i];
	nums[i] = nums[j];
	nums[j] = temp;
}

4.归并排序

Note:分治思想,实践(小和问题和逆序对问题)

时间复杂度: O ( N l o g N ) O(NlogN) O(NlogN) 空间复杂度: O ( N ) O(N) O(N) *“内部缓存法”*可以改善

master公式求时间复杂度,递归思想

class Sol {
public:
	void merge_sort(vector<int> &nums) {
		if (nums.empty() || nums.size() < 2) {
			return;
		}
		sortProcess(nums, 0, nums.size() - 1);
	}

	void sortProcess(vector<int> &nums, int L, int R) {
		if (L == R) {
			return;
		}
		int mid = L + ((R - L) >> 1); // L和R中间的位置(L + R) / 2
		sortProcess(nums, L, mid); // T(n/2)
		sortProcess(nums, mid + 1, R); // T(N/2)
		merge(nums, L, mid, R); // O(N)
		// T(N) = 2 T(N/2) + O(N)
	}

	void merge(vector<int> &nums, int L, int mid, int R) {
		vector<int> help(R - L + 1);
		int i = 0;
		int p1 = L;
		int p2 = mid + 1;
		while (p1 <= mid && p2 <= R) {
			help[i++] = nums[p1] < nums[p2] ? nums[p1++] : nums[p2++];
		}
		//两个必有一个越界
		while (p1 <= mid) {
			help[i++] = nums[p1++];
		}
		while (p2 <= R) {
			help[i++] = nums[p2++];
		}
		for (i = 0; i < help.size(); i++) {
			nums[L + i] = help[i];
		}
		return;//这里应该有return
	}
};

引出快排——荷兰国旗

vector<int> partition(vector<int> &nums, int L, int R, int num){
	int less = L - 1;
	int more = R + 1;
	int cur= L;
	vector<int> res;
	while(cur < more){
		if(nums[cur] < num){
			swap(nums, ++less, cur++);
		}else if(nums[cur] > num){
			swap(nums, --more, cur);
		}else{ // == num
			L++;
		}
	}
	return vector<int> {less + 1, more - 1};
}

void swap(vector<int> &nums, int i, int j){
	int temp = nums[i];
	nums[i] = nums[j];
	nums[j] = temp;
}

5.快速排序

Note:分治思想,实践(荷兰国旗),partition,工程上必须改成非递归形式,自己压栈

随机快排更好,避开数据状况,可以分堆更好的概率更高

时间复杂度: O ( N l o g N ) O(NlogN) O(NlogN) 空间复杂度: O ( l o g N ) O(logN) O(logN) // 随机快排的,记录断点的空间

随机快排是用到最多的地方,常数项少,导致更快,比merge快

// 经典快排
void quick_sort(vector<int> &nums, int L, int R){
	if(L < R){
		srand((unsigned)time(NULL));//随机种子
		swap(nums, L + rand() % (R - L + 1), R)// 随机快排
		vector<int> p = partition(nums, L, R);
		quick_sort(nums, L, p[0] - 1);
		quick_sort(nums, p[1] + 1, R);
	}
}

// 划分,时间O(N),空间O(1)
vector<int> partition(vector<int> &nums, int L, int R){
	int less = L - 1;
	//int more = R + 1;
	int more = R; // 和上面的不一样的原因是,最后一个nums[R]是不能改变的,直接先把x扩进大于的区域,炫技——可以省去一个变量
	int cur = L;
	while(cur < more){
		if(nums[cur] < arr[R]){
			swap(nums, ++less, cur++);
		}else if(nums[cur] > arr[R]){
			swap(nums, --more, cur);
		}else{
			cur++;
		}
	}
	swap(nums, more, R); // 这一句很重要,和上面的不太一样
	return vector<int> {less + 1, more};
}

void swap(vector<int> &nums, int i, int j){
	int temp = nums[i];
	nums[i] = nums[j];
	nums[j] = temp;
}

6.堆排序

Note:,完全二叉树,满二叉树,大根堆,小根堆
抽象——数组->树

左子 2 ∗ i + 1 2*i+1 2i+1, 右子 2 ∗ i + 2 2*i+2 2i+2, 父 ( i − 1 ) / 2 (i-1)/2 (i1)/2

完全二叉树 节点和高度公式: h = l o g N h=logN h=logN,单个加进来 l o g i logi logi,所有节点加进来 l o g 1 + l o g 2 + . . . = O ( N ) log1+log2+...=O(N) log1+log2+...=O(N)

时间复杂度: O ( N l o g N ) O(NlogN) O(NlogN) 空间复杂度: O ( 1 ) O(1) O(1)

void heap_sort(vector<int> &nums){
	if(nums.empty() || nums.size() < 2){
		return;
	}
	//建立大根堆 复杂度:O(N)
	for(int i = 0; i < nums.size(); i++){
		heapInsert(nums, i);
	}
	
	int heapSize = nums.size();
	swap(nums, 0, --heapSize);
	while(heapSize > 0){
		heapify(nums, 0, heapSize);
		swap(nums, 0, --heapSize);
	}
}

// 变成大根堆 复杂度:O(logN)
void heapInsert(vector<int> &nums, int Index){
	while(nums[index] > nums[(index - 1) / 2]){
		swap(nums, index, (index - 1) / 2);
		index = (index - 1) / 2;
	}
}

// 下沉
void heapify(vector<int> nums, int index, int heapSize){
	int left = index * 2 + 1;
	while(left < heapSize){
		int largest = left + 1 < heapSize && nums[left + 1] > nums[left] ? left + 1 : left;
		largest = nums[largest] > nums[index] ? largest : index;
		if(largest == index){
			break;
		}
		swap(nums, largest, index);
		index = largest;
		left = index * 2 + 1;
	}
}

void swap(vector<int> &nums, int i; int j){
	int temp = nums[i];
	nums[i] = nums[j];
	nums[j] = temp;
}

排序稳定性

保证原始的相同顺序不变

具有稳定性:冒泡,插入,归并

不具有稳定性:选择排序(merge),快排(partition),堆排(建立大根堆)

综合排序

短(< 60):插排
基础类型:快排
自定义类型:堆排

比较器

桶排序:计数排序,基数排序:不重要

时间复杂度: O ( N ) O(N) O(N) 空间复杂度: O ( N ) O(N) O(N)

稳定

非基于比较的排序

计数排序:词频
基数排序:按位

例:排序后,相邻数组最大值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值