排序算法总结

之前学习了排序算法,现总结如下:
1.快速排序
该算法采用分治的思想,主要步骤如下所示:
 1)选取一个基准数pivot(一般选取第一个数,如果是随机选取的,将该值与第一个值进行交换)
 2)遍历数组,将所有比pivot小的值放在pivot左边,将比pivot大的值放在pivot的右边,该过程称为分区partition,此过程借助双指针实现
 3)根据pivot将数组分为两部分,重复上述过程

int Partition(vector<int>& v, int left, int right)
{
	int pivot = v[left]; // 确定基准数
	while (left < right)
	{
		// 如果值大于等于pivot,right前移
		while (left < right && v[right] >= pivot)
		{
			right--;
		}
		if (left < right)
		{
			v[left] = v[right];
			left++;
		}
		// 因为left前的数保证了值小于pivot,因此left前移
		while (left < right && v[left] <= pivot)
		{
			left++;
		}
		if (left < right)
		{
			v[right] = v[left];
			right--;
		}
	}
	// left == right
	v[left] = pivot;
	return left;
}

void QuickSort(vector<int>& v,int left,int right)
{
	if (left >= right) return;
	int mid = Partition(v, left, right);
	QuickSort(v, left, mid - 1);
	QuickSort(v, mid + 1, right);
}

时间复杂度:平均情况下为O(nlogn),其中N为数据长度,递归调用的深度为O(logn)。时间复杂度最坏的情况为O(n2),例数组有序时每次都选择最左侧或者最右侧为基准数时。
空间复杂度:O(logn),递归调用所需要的栈空间,最差为O(n)
稳定性:该算法是不稳定的。稳定指排序前a==b,且a在b的前面,则排序后a仍在b的前面。以arr = [1,2,2,3]为例,假设选择arr[2]为基准数,将大于等于基准数的元素放到基准数的右侧,则arr[1]会放到arr[2]的右侧,同理,选择arr[1]为基准数,将小于等于基准数的元素放到基准数的左侧,则数组中的两个2也非原序。本质上是因为不断交换left和right指针的元素,导致算法不稳定。

2.冒泡排序
每次遍历时,比较相邻元素,不符合排序准则即交换,直至将当前最大(小)元素放置到合适的位置。

void BubbleSort(vector<int>& v)
{
	for (int i = 0; i < v.size()-1; i++) // 遍历次数
	{
		for (int j = 0; j < v.size()-1 - i; j++)// 当前遍历范围下的最大值
		{
			if (v[j] > v[j + 1])
			{
				int temp = v[j];
				v[j] = v[j + 1];
				v[j + 1] = temp;
			}
		}
	}
}

时间复杂度:O(n2)
空间复杂度:O(1)
稳定性:稳定,只交换相邻元素,不改变序列原序。

3.选择排序
初始时,在整个序列中查找最小(大)值,放在元素的初始位置作为已排序元素,然后再遍历查找当前未排序元素的最小(大)值,将其放到已排序元素中,查找过程只进行一次交换。

void SelectSort(vector<int>& v)
{
	for (int i = 0; i < v.size() - 1; i++) // 已排序元素个数
	{
		int minIndex = i;
		for (int j = i+1; j < v.size(); j++)  
		{
			if (v[j] < v[minIndex])
			{
				minIndex = j;
			}
		}
		if (minIndex != i)
		{
			int temp = v[i];
			v[i] = v[minIndex];
			v[minIndex] = temp;
		}
	}
}

时间复杂度:O(n2)
空间复杂度:O(1)
稳定性:不稳定,由于交换导致原序会发生改变。

4.插入排序
对于第n个元素,假设前面n-1个元素都已经排好序,则将当前元素插入到有序序列中的合适的位置。

void InsertSort(vector<int>& v)
{
	for (int i = 1; i < v.size(); i++) // 当前处理的元素
	{
		for (int j = i; j > 0; j--)    // 遍历已排序序列,将当前元素插入
		{
			if (v[j] < v[j - 1])
			{
				int temp = v[j];
				v[j] = v[j - 1];
				v[j - 1] = temp;
			}
		}
	}
}

时间复杂度: O(n2),最差的情况为原序列为降序(升序)序列,但需要返回升序(降序)序列,最好的情况为原序列为降序(升序)序列,但需要返回降序(升序)序列,
空间复杂度: O(1)
稳定性:稳定,按照原序列顺序进行操作。
5.希尔排序
该算法是对直接插入排序的一种改进算法,将记录按照下标的一定增量分组,对每组使用直接插入排序,随着增量的减小,每组包含的元素增大,直至增量为1时,排序完成,因此也称为缩小增量排序。一般情况下选择增量为{n/2,(n/2)/2,……},称为希尔增量。

void ShellSort(vector<int>& v)
{
	for (int step = v.size() / 2; step >= 1; step /= 2)   // 希尔增量
	{
		for (int k = 0; k < step; k++)                    // 多少组
		{
			for (int i = k+step; i < v.size(); i += step) // 对每组进行插入排序 
			{
				for (int j = i; j > k; j -= step)
				{
					if (v[j] < v[j - step])
					{
						int temp = v[j];
						v[j] = v[j - step];
						v[j - step] = temp;
					}
				}
			}
		}
	}
}

时间复杂度:小于等于O(n2)
空间复杂度:O(1)
稳定性:不稳定,分组插入排序时会导致原序发生改变

6.归并排序
该算法采用分治的思想,分是指将大问题拆分为小问题,治是指将小问题的结果进行合并,分为递归和迭代两种实现方法。
递归:从上向下

// 合并
void Merge(vector<int>& v, int left, int mid, int right)
{
	int len = right - left + 1;
	vector<int> temp(len);      // 生成新的数组保存排序结果
	int i = left, j = mid + 1, k = 0;   // 三个序列的开始
	while (i <= mid && j <= right)      // 当两个序列都有值时
	{
		temp[k++] = v[i] < v[j] ? v[i++] : v[j++];  // 先赋值,再移动下标位置
	}
	while (i <= mid)    // 将还没有遍历完的序列赋值给temp
	{
		temp[k++] = v[i++]; 
	}
	while (j <= right)
	{
		temp[k++] = v[j++];
	}
	for(int index = 0;index<len;index++)
	{
		v[left+index] = temp[index];
	}
}
void MergeSort01(vector<int>& v,int left, int right)
{
	if (left < right)
	{
		int mid = left + (right - left) / 2;
		MergeSort01(v, left, mid);
		MergeSort01(v, mid + 1, right);
		Merge(v, left, mid, right);
	}
}

迭代:从下到上

void MergeSort02(vector<int>& v)
{
	int len = v.size();
	for (int step = 1; step <= len; step <<= 1) // 步长
	{
		int offset = step + step;
		for (int i = 0; i < v.size(); i += offset)  // 分组
		{
			Merge(v, i, min(i + step-1,len-1), min(i + offset-1,len-1));
		}
	}
}

时间复杂度:O(nlogn)
空间复杂度:O(n),保存排序后元素
稳定性:稳定,都是相邻的元素或组进行操作

7.堆排序
利用堆的特性进行排序,堆是近似一种完全二叉树的结构,利用数组进行保存,对于节点i,其父节点为(i-1)/2,其左孩子为2i+1,右孩子为2i+2,且其父节点对应的值大于(小于)其子节点对应的值。
算法过程:
1)根据给定序列建堆,升序建立大根堆,降序建立小根堆
2)将堆顶元素与最后一个元素进行交换
3)交换后堆顶元素可能违反堆的性质,因此从堆顶向下调整新堆。重复2)和3)直至排序完成。

void AdjustHeap(vector<int>& v,int index,int len)
{
	int child = 2 * index + 1; // 左孩子
	while (child < len)
	{
		if (child+1<len && v[child] < v[child + 1]) child++; // 选择左右孩子中较大的那个
		if (v[index] >= v[child]) break;                     // 父节点大于等于孩子节点中较大的值,即符合堆性质,停止调整
		swap(v[index], v[child]);                            // 交换父节点和孩子节点
		index = child;                                       // 以孩子节点为当前节点继续向下调整
		child = 2 * index + 1;
	}
}

void MakeHeap(vector<int>& v)
{
	// 从最后一个父节点进行建堆
	for (int i = v.size()/2-1; i >= 0; i--)
	{
		AdjustHeap(v, i, v.size());
	}
}

void HeapSort(vector<int>& v)
{
	// 建堆
	MakeHeap(v);
	
	for (int i = v.size() - 1; i >= 0; i--)
	{
		// 交换
		swap(v[0], v[i]);
		// 调整新堆
		AdjustHeap(v, 0,i);
	}
}

时间复杂度:O(nlogn),交换后每个节点都向下调整,最多为logn层
空间复杂度:O(1)
稳定性:不稳定,因为发生了非相邻元素的交换

8.计数排序
该算法不是基于比较的排序算法,该算法是根据给定序列的元素范围生成数组,并将序列中元素个数记录在数组中,根据数组的统计结果来进行排列,即通过增加空间来降低时间复杂度。

void CountSort(vector<int>& v)
{
	// 求解待排序元素中的最大值和最小值
	int MaxValue = v[0];
	int MinValue = v[0];
	int len = v.size();
	for (int i = 1; i < len; i++)
	{
		if (v[i] < MinValue)
		{
			MinValue = v[i];
		}
		else if(v[i]>MaxValue)
		{
			MaxValue = v[i];
		}
	}
	// 生成新的数组,并将对待排序元素进行计数
	vector<int> count(MaxValue - MinValue + 1); 
	for (int i = 0; i < len; i++)
	{
		count[v[i] - MinValue]++;
	}
	// 遍历count数组进行排序
	int k = 0;
	for (int i = 0; i < MaxValue - MinValue + 1; i++)
	{
		while (count[i] > 0)
		{
			v[k++] = i + MinValue;
			count[i]--;
		}
	}
}

时间复杂度:O(n+k),输入的元素种类数为k,元素个数为n
空间复杂度:O(k),生成数组保存每个元素的个数
稳定性:稳定

9.桶排序
桶排序对计数排序进行升级,将待排序序列平均分配到有限个桶内,再对每个桶内的数据进行排序,再将每个桶内的排序结果拼接起来。

void BucketSort(vector<int>& v,int bucketSize)
{
	if (v.size() <= 1) return;
	vector<vector<int>> bucket(bucketSize);
	// 求解序列中的最大值最小值
	int minValue = v[0];
	int maxValue = v[0];
	for (int i = 0; i < v.size(); i++)
	{
		if (v[i] < minValue) minValue = v[i];
		else if (v[i] > maxValue) maxValue = v[i];
	}

	// 将序列中的数分配到桶中
	int bucketCount = (maxValue - minValue) / bucketSize+1;  // 每个桶内存储的数目
	for (int i = 0; i < v.size(); i++)
	{
		bucket[(v[i] - minValue) / bucketCount].push_back(v[i]);
	}

	v.resize(0);
	for (int i = 0; i < bucketSize; i++)
	{
		if (bucket[i].size() != 0)
		{
			sort(bucket[i].begin(), bucket[i].end());
			for (int j = 0; j < bucket[i].size(); j++)
			{
				v.push_back(bucket[i][j]);
			}
		}
	}
}

可以看出桶数量越多,每个桶内数据越少,排序所用时间越少,但是相应所需空间增大。
10.基数排序
该算法按照关键字各位的值对n个元素进行若干趟分配和收集从而完成排序,主要分为两种,第一种,从低位到高位进行排序(LSD),第二种,从高位到低位(MSD)。
LSD:

int getMaxDigit(vector<int>& v)
{
	int maxValue = v[0];
	for (int i = 1; i < v.size(); i++)
	{
		if (v[i] > maxValue) maxValue = v[i];
	}
	int digit = 1;
	while (maxValue / 10)
	{
		digit++;
		maxValue /= 10;
	}
	return digit;
}

// 提取key中digit位的值
int getValue(int key,int digit)
{
	while (digit != 1)
	{
		key /= 10;
		digit--;
	}
	return key % 10;
}

// LSD
void RadixSort_LSD(vector<int>& v)
{
	if (v.size() <= 1) return;
	int len = v.size();
	// 当前序列中的最大位数
	int digit = getMaxDigit(v);
	// 从低位到高位:分配,收集
	for (int k = 1; k <= digit; k++)
	{
		vector<int> temp(len);
		vector<int> count(10, 0);
		for (int i = 0; i < len; i++)
		{
			count[getValue(v[i], k)]++;
		}
		// 累加
		for (int i = 1; i < 10; i++)
		{
			count[i] += count[i - 1];
		}
		// 重新排序,从后向前,保持稳定性
		for (int i = len-1; i >=0; i--)
		{
			int cur = getValue(v[i], k);
			int index = count[cur];
			count[cur]--;
			temp[index - 1] = v[i];
		}
		for (int i = 0; i < len; i++)
		{
			v[i] = temp[i];
		}
	}
}

MSD:

void RadixSort_MSD(vector<int>& v, int digit)
{
	int len = v.size();
	vector<vector<int>> Radix(10);
	//分配
	if (len > 1 && digit >= 1)
	{
		for (int i = 0; i < len; i++)
		{
			int r = getValue(v[i], digit);
			Radix[r].push_back(v[i]);
		}
		// 收集
		for (int i = 0, j = 0; i < 10; i++)
		{
			RadixSort_MSD(Radix[i], digit - 1);
			while (!Radix[i].empty())
			{
				v[j++] = Radix[i].front();
				Radix[i].erase(Radix[i].begin());
			}
		}
	}
}

时间复杂度:O(k*n),k表示最大的位数,n为序列个数,LSD时每一位都需要对n个元素进行分配收集,MSD的递归深度为k
空间复杂度:O(n+k),需要O(n)来保存收集的数据(MSD中是桶的空间)
稳定性:稳定

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值