常见的排序方法以及C++实现

本文深入解析了八种经典排序算法,包括冒泡排序、选择排序、插入排序、希尔排序、堆排序、归并排序、快速排序和基数排序,详细阐述了每种算法的基本思想、实现代码及时间复杂度分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

时间复杂度与空间复杂度:
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度;
算法的时间复杂度反映了程序执行时间随输入规模增长而增长的量级
1:冒泡排序
思想:两两比较相邻记录,反序则往后移动,正序则不动
传统冒泡排序

void BubbleSort(vector<int> &v) {
	int len = v.size();
	for (int i = 0; i < len; i++) {
		for (int j = 0; j < len - i - 1; j++) {
			if (v[j] > v[j + 1])
				SWAP(v[j], v[j + 1]);
		}
	}
	return;
}

双向冒泡排序

void bidBubbleSort(vector<int>&v) {
	int len = v.size();
	int right = len - 1, left = 0;
	while (right > left) {
		int l = left + 1, r = right - 1;
		for (int i = left; i < right; i++) {
			if (v[i] > v[i + 1]) {
				SWAP(v[i], v[i + 1]);
				r = i;
			}
		}
		right = r;
		for (int j = right; j > left; j--) {
			if (v[j] < v[j - 1]) {
				SWAP(v[j], v[j - 1]);
				l = j;
			}
		}
		left = l;
	}
	return;
}

时间复杂度:O(n^2)
2.选择排序
基本思想: 在第i个值的确定中通过n-i-1次的值比较,确定第i个值

void SelectSort(vector<int>&v) {
	int len = v.size();
	for (int i = 0; i < len - 1; i++) {
		int min = i;
		for (int j = i + 1; j < len - 1; j++) {
			if (v[min] > v[j])
				min = j;
		}
		if (min != i)
			SWAP(v[i], v[min]);
	}
	return;
}

时间复杂度:O(n^2),交换移动数据次数减少
3.插入排序:
基本思想:将一个记录插入已经排好序的有序数组中

void InsertSort(vector<int>&v) {
	int len = v.size();
	for (int i = 1; i < len ; i++) {
		if (v[i] < v[i - 1]) {
			int temp = v[i];
			int j = i - 1;
			for (; j >= 0 &&v[j] > temp; j--)
				v[j + 1] = v[j];
			v[j+1] = temp;
		}	
	}
	return;
}

时间复杂度:O(n^2),略好于选择排序,冒泡排序
4.希尔排序:
基本思想:有间隔的插入排序,首先确定间隔,间隔就是gap = len/2,然后做插入排序

void ShellSort(vector<int>&v) {
	int len = v.size();
	int gap = len / 2;
	while (gap > 0) {
		for (int i = 0; i < len - gap; i++) {
			int tmp = v[i+gap];
			int j = i;
   //插入排序的思想
			for (; j >= 0 && tmp < v[j]; j = j - gap) 
				v[j + gap] = v[j];
			v[j + gap] = tmp;
		}
  //每次插入排序结束之后,将间隔缩小一半
		gap = gap / 2;
	}
	return;
}

时间复杂度:O(nlogn~n^2),但不是一种稳定的排序方式
5:堆排序
思想:首先构建大顶堆
1:大顶堆的构建:大顶堆表示每个节点的值都大于或者等于其左右子树,且是完全二叉树。所以非叶子节点的下标小于等于len/2-1(len代表数组长度);因此就只要以非叶子节点作为下标构建大顶堆即可(按照从下到上的顺序构建大顶堆)

void CreatBigHeap(vector<int>&v,int len,int index) {
	int maxid = index;
	int left = 2 * index + 1, right = 2 * index + 2;
	if (left < len  && v[maxid] < v[left])
		maxid = left;
	if (right < len && v[maxid] < v[right])
		maxid = right;

	if (maxid != index) {
		SWAP(v[index], v[maxid]);
		CreatBigHeap(v, len, maxid);  
       //防止其中一个非叶节点的条件满足之后破坏其他非叶节点的条件
	}
	return;
}

2:将大顶堆的第一个节点与最后一个节点交换,然后重构大顶堆,需要注意的是没次交换之后,重构数组的长度减1,知道大顶堆需要重构的长度只有1结束
void HeapSort(vector<int>&v) {
	int len = v.size();
	//第一次创建大顶堆
	for (int i = len / 2 - 1; i >= 0; i--) 
		CreatBigHeap(v, len, i);
	for (int j = len - 1; j >= 1;j-- ) {
		SWAP(v[j], v[0]);
		CreatBigHeap(v,j,0);
	}
	return;
}

复杂度分析:O(nlogn),不稳定
6:归并排序
基本思想:将一组数据两两分开,然后两两归并排序,最后排在一起

void MergeSortDetail(vector<int>&data, vector<int> &copy, int start, int end) {
	if (start == end)
		return;
	int mid = (start + end) / 2;
	MergeSortDetail(copy,data,start,mid);
	MergeSortDetail(copy, data, mid + 1, end);
	int i = mid, j = end;
	int index = end;
	while (i >= start&&j >= mid+1) {
		if (data[i] > data[j]) {
			copy[index--] = data[i--];
		}
		else
		copy[index--] = data[j--];
	}
	for (; i >= start; i--) 
		copy[index--] = data[i];
	for (; j >= mid + 1; j--)
		copy[index--] = data[j];
	return;
}
void MergeSort(vector<int>&data) {
	vector<int> copy = data;
	int len = data.size();
	int start = 0, end = len - 1;
	MergeSortDetail(copy,data,start,end);
	return;
}

在这段代码中需要注意的是在MergeSort函数中我们需要一个辅助的vector容器(copy),核心就在于递归调用MergeSortHelp函数的时候每次改变copy其实就对应于上一次递归调用的data,因此返回上一次调用的递归函数时其实data就变成了有序的,当然这里的有序并不是说整个data是有序的,因为我们一开始会把这个data分成两半,这里的有序指的是在这一次递归调用中每一半都是有序的,整体不一定有序,这样递归一直到最后一次,data在分界点的两端分别是有序,因此通过copy来调整,将这些值有序的放入copy中(不是data),最后返回值是copy!!!,如果你返回data你会得到一个分界点两端分别有序的数组。
复杂度分析:O(nlogn),稳定,递归的方法空间复杂度O(n+logn),非递归空间复杂度O(n)
7:快速排序
基本思想:对于一个数组,首先确定最右边的数为轴,初始化最左边的下标为第一个索引J,然后令i在J的后面,从最左边的第一个数开始于最右边的轴做比较,如果大于,则不作任何动作,继续移动J,如果小于的话把i,J下标对应的值作交换,并且I向前移动一个单位;最后可以得到J的下标必定大于等于i,而且从开始到I的位置的数值都是小于最右边的那个轴,从i+1到轴之前的数都是大于轴的,所以交换i+1与轴所对应的数,然后继续递归排序最左边到下标为i的数,和i+2到最右边的数。

void QuickSort(vector<int>&data, int left, int right) {
	int len = data.size();
	int i = left -1, j = left;
	int tmp = data[right];
	if (right > left) {
		for (; j < right; j++) {
			if (data[j] < tmp) {
				i++;
				SWAP(data[i], data[j]);
			}
		}
		SWAP(data[j], data[i + 1]);
		QuickSort(data, left, i);
		QuickSort(data, i + 2, right);
	}
	return;
}

复杂度分析:时间复杂度O(nlogn),不稳定,空间复杂度O(n~logn)
8:基数排序
(LSD)基本思想:将一系列数的个位数取出来按照大小排序,然后得到的就是按照个位数排序的一串数组,然后在根据十位数,对上次按照个位数排序的数组取按照十位数排序的一串数组得到结果
73, 22, 93, 43, 55, 14, 28, 65, 39, 81(原始数组)
81,22,73,73,43,14,55,65,28,39(按照个位数排序的结果)
14,22,28,39,43,55,65,73,81,93(按照上面得到的数组一次取出十位数排序的数组)


void LSD(vector<int>& data) {
	int len = data.size();
	map<int, vector<int>>m;
	
	int radix = 0, div = 1;
	while (div <= 10) {
		for (int i = 0; i < len; i++) {
			radix = (data[i] / div) % 10;
			m[radix].push_back(data[i]);
		}
		data.clear();
		for (map<int, vector<int>>::iterator it = m.begin(); it != m.end(); it++) {
			for (vector<int>::iterator it1 = it->second.begin(); it1 != it->second.end(); it1++) {
				data.push_back(*it1);
			}
		}
		m.clear();
		div *= 10;
	}
	return;
}

复杂度分析:
时间复杂度可以表示为O(Kn)。其中n就是待排序序列的元素个数,K是数字的位数,但是在一般情况下,K并不能再被认为是个常数。那K应该是什么呢。这里我们以十进制的数为例。整数序列中的每个数是以10为底的数。不妨我们用b记为底数,即b=10。那如果整个整数序列中的最大数是N。那这就很容易看出,K= logbN。所以在一般情况下,基数排序的时间复杂度可以看做是O(n logbN)。在N非常大的情况下是不是基数排序的效率要低于比最优的比较型的排序算法(最优的比较型的排序算法的时间复杂度是O(nlogn))
LSD的基数排序适用于位数小的数列,如果位数多的话,使用MSD的效率会比较好,MSD的方
式恰与LSD相反,是由高位数为基底开始进行分配,其他的演 算方式则都相同。

代码中有不对的地方还希望大佬们指出来~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值