学习记录—C++实现排序算法汇总

排序分类

首先分为内部排序和外部排序,本文主要研究内部排序。

外部排序排序过程需要访问外存,内存外存也要结合使用,适合处理数据量很大的数据,
如1000T等,使用多路归并排序算法和败者树。

内部排序按照原理分为5大类。插入,选择,交换,归并,基数排序。

插入排序:直接插入排序,折半插入排序,希尔排序。

选择排序:简单选择排序,堆排序。

交换排序:冒泡排序,快速排序。

归并排序。

计数排序。

桶排序。

基数排序。

排序总结

时间复杂度:算法执行的时间
空间复杂度:算法执行占用的内存
排序方式:in-place(占用常数内存,不占用额外内存),out-place(占用额外内存)
稳定性:原来相等的两个数排序后不乱序为稳定
在这里插入图片描述

一、冒泡排序

冒泡排序就是把⼩的元素往前调或者把⼤的元素往后调,比较是相邻的两个元素比较,交换也发⽣在这两个元素之间。

法一

//时间复杂度:O(n2)
//空间复杂度:O(1)
// 稳定
using std::cout;
using std::endl;
using std::vector;
using std::swap;
void print(vector<int>& nums) {
	 for (auto i : nums) {
	 cout << i << " ";
	 }
	 cout << endl;
}

void bubbleSort(vector<int>& nums) {
 int n = nums.size();
 bool flag = false;
 for (int i = 0; i < n - 1; ++i) {//i = 0 起,循环了n - 1趟,更符合规范理解
 //for (int i = 0; i < n; ++i) {//i = 0 起,循环了n 趟,不影响结果
 flag = false;
 for (int j = 0; j < n - 1 - i; ++j) {
	 if (nums[j] > nums[j + 1]) {
	 //某⼀趟排序中,只要发⽣⼀次元素交换,flag就从false变为了true
	 //也即表示这⼀趟排序还不能确定所剩待排序列是否已经有序,应继续下⼀趟循环
		 swap(nums[j], nums[j + 1]);
		 flag = true;
	 }
 }
 //但若某⼀趟中⼀次元素交换都没有,即依然为flag = false
 //那么表明所剩待排序列已经有序
 //不必再进⾏趟数⽐᫾,外层循环应该结束,即此时if (!flag) break; 跳出循环
 if (!flag) { break; }
 }
}
int main() {
	 vector<int> nums = { 8,9,1,4,2,3,6,7,5,5 };
	 bubbleSort(nums);
	 print(nums);
	 return 0;
}

法二

#include <iostream>
#include <vector>
using namespace std;
//时间复杂度:O(n2)
//空间复杂度:O(1)
// 稳定
 
void  bubble_Sort(vector<int>& nums)
{
	int n = nums.size();
    bool valid = true;
	for (int i = 0; i < n&&valid ; i++)
	{
        valid=false;
		for (int j = n - 2; j >= i; j--)//气泡一样往前冒
		{
			if (nums[j] > nums[j + 1])
			{
			  swap(nums[j], nums[j + 1]);
              valid=true;//交换成功为true
			//	int temp = nums[j];
			//	nums[j] = nums[j + 1];
			//	nums[j + 1] = temp;
			}
		}
	}
}

int main()
{
	vector<int>v{ 1,10,24,12,16,8,18,89,25 };
	bubble_Sort(v);
	cout<<"排序从小到大:" << endl;
	for(vector<int>::iterator it=v.begin(); it!=v.end();it++)
	{
		cout << *it << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

二、插入排序:类似打扑克牌

插⼊排序是在⼀个已经有序的⼩序列的基础上,⼀次插⼊⼀个元素。

//直接插入排序
//稳定
//原始数据越有序,排序时间越快
//时间复杂度最好O(n):本来有序
//时间复杂度最坏O(n2):逆序
//空间复杂度O(1):一个哨兵额外空间
// 优化地方
//1)比较次数
//2)移动次数

#include <iostream>
#include <vector>
using namespace std;

//直接插入:顺序查找插入位置
//void Insert_Direct(vector<int>&s)
//{
//	int n = s.size();
//	for (int i = 1; i < n; i++)
//	{
//		if (s[i] < s[i - 1])//如果后一个比前一个大,就不用移动了
//		{
//			int j=0;
//			int temp = s[i];//哨兵
//			for (j = i - 1; j>=0&&s[j] > temp; j--)
//			{
//				s[j + 1] = s[j];
//			}
//			s[j + 1] = temp;
//		}
//	}
//}

//折半插入排序
//时间和空间复杂度不变
//n较大时,减小比较次数查找快,移动次数没有减小
//二分查找插入位置
void Insert_Binary(vector<int>& s)
{
	int n = s.size();
	for (int i = 1; i < n; ++i)
	{
		int temp = s[i];//哨兵
		int low = 0, high = i - 1;
		while (low <= high)
		{
			int mid = low + (high - low) / 2;
			if (temp > s[mid])
			{
				low = mid + 1;
			}
			else
			{
				high = mid - 1;
			}
		}
		for (int j = i - 1; j >= high + 1; --j)
		{
			s[j + 1] = s[j];
		}
		s[high+ 1] = temp;
	}
}

int main()
{
	vector<int>v{ 1,10,24,12,16,8 };
	//Insert_Direct(v);
	Insert_Binary(v);
	cout<<"排序从小到大:" << endl;
	for(vector<int>::iterator it=v.begin(); it!=v.end();it++)
	{
		cout << *it << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

三、选择排序

选择排序是给每个位置选择当前元素最⼩的,⽐如给第⼀个位置选择最⼩的,在剩余元素⾥⾯给>⼆个元素选择第⼆⼩的,依次类推,直到第n-1个元素,第n个 元素不⽤选择了,因为只剩下它⼀个最⼤的元素了。

//简单选择排序
//时间复杂度:O(n2)
//空间复杂度:O(1)
//不稳定
//待排序数据元素中选出最小(最大)元素,直接放在起始位置
//剩余未排序的元素中找到最小(最大)元素,放在已排序序列的末尾

#include <iostream>
#include <vector>
using namespace std;  

void  choose_Sort(vector<int>& nums)
{
	int n = nums.size();
	//外层循环完成数据交换,内层循环完成数据比较
	for (int i = 0; i < n; i++)
	{
		int min = i;
		for (int j = i + 1; j < n; j++)
		{
			if (nums[j] < nums[min])
			{
				min = j;
			}
		}
		if (min != i)
		{
			swap(nums[min], nums[i]);
		}
	}
}

int main()
{
	vector<int>v{ 1,10,24,12,16,8,18,89,25 };
	choose_Sort(v);
	cout<<"排序从小到大:" << endl;
	for(vector<int>::iterator it=v.begin(); it!=v.end();it++)
	{
		cout << *it << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

四、希尔排序

希尔排序可以说是插⼊排序的⼀种变种。⽆论是插⼊排序还是冒泡排序,如果数组的最⼤值刚好是在第⼀位,要将它挪到正确的位置就需要 n - 1 次移动。也就是说,原数组的⼀个元素如果距离它正确的位置很远的话,则需要与相邻元素交换很多次才能到达正确的位置,这样是相对比较花时间了。
希尔排序就是为了加快速度简单地改进了插⼊排序,交换不相邻的元素以对数组的局部进⾏排序。
希尔排序的思想是采⽤插⼊排序的⽅法,先让数组中任意间隔为 h 的元素有序,刚开始 h 的⼤⼩可以是 h = n / 2,
接着让 h = n / 4,让 h ⼀直缩⼩,当 h = 1 时,也就是此时数组中任意间隔为1的元素有序,此时的数组就是有序的了

//希尔排序
//时间复杂度O(n^1.25)~O(1.6n^1.25)
//空间复杂度O(1)
//跳跃式接近,慢慢的有序,最后一次少量移动
//增量序列是递减的,互质的,最后一个增量值是1
#include <iostream>
#include <vector>
using namespace std;

void  shell_Sort(vector<int>& s)
{
	int n = s.size();
	int dk = n;
	do
	{
		dk = dk / 3 + 1;//dk步长
		for (int i = dk; i < n; i += dk)
		{
			if (s[i] < s[i - dk])
			{
				int temp = s[i];
				int j = 0;
				for (j = i - dk; j >= 0 && s[j] > temp; j -= dk)
				{
					s[j + dk] = s[j];
				}
				s[j + dk] = temp;
			}
		}
	} while (dk > 1);//dk=1跳出
}

int main()
{
	vector<int>v{ 1,10,24,12,16,8,18,89,25 };
	shell_Sort(v);
	cout<<"排序从小到大:" << endl;
	for(vector<int>::iterator it=v.begin(); it!=v.end();it++)
	{
		cout << *it << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

五、快速排序

1、选取第⼀个数为基准
2、将⽐基准⼩的数交换到前⾯,⽐基准⼤的数交换到后⾯
3、对左右区间重复第⼆步,直到各区间只有⼀个数
我们从数组中选择⼀个元素,我们把这个元素称之为中轴元素吧,然后把数组中所有⼩于中轴元素的元素放在其左边,所有⼤于或等于中轴元素的元素放在其右边,显然,此时中轴元素所处的位置的是有序的。也就是说,我们⽆需再移动中轴元素的位置。
从中轴元素那⾥开始把⼤的数组切割成两个⼩的数组(两个数组都不包含中轴元素),接着我们通过递归的⽅式,让中轴元素左边的数组和右边的数组也重复同样的操作,直到数组的⼤⼩为1,此时每个元素都处于有序的位置。

//快速排序算法
// 不稳定
//原始数据越无序,排序时间越快
//时间复杂度:O(nlog(n))
//空间复杂度:O(n)
#include <iostream>
using namespace std;
#include <vector>

//递归快速排序,从小到大
//void Quick_Sort(vector<int>& s,int low,int high)
//{
//	if (low >= high) return;
//	int i, j, base, temp;
//	i = low, j = high;
//	base = s[low];//取最左边的数为基准数
//	while(i<j)
//	{
//		while (s[j] >= base && i < j) j--;
//		while (s[i] <= base && i < j) i++;
//		if (i < j)
//		{
//			temp = s[i];
//			s[i] = s[j];
//			s[j]= temp;			
//		}	
//	}
//	//基准数归位
//	s[low] = s[i];
//	s[i] = base;
//	Quick_Sort(s, low, i - 1);//递归左子表
//	Quick_Sort(s, i+ 1, high);//递归右子表
//}

递归快速排序,从大到小
//void Quick_Sort(vector<int>& s, int low, int high)
//{
//	if (low >= high) return;
//	int i, j, base, temp;
//	i = low, j = high;
//	base = s[low];//取最左边的数为基准数
//	while (i < j)
//	{
//		while (s[j] <= base && i < j) j--;
//		while (s[i] >= base && i < j) i++;
//		if (i < j)
//		{
//			temp = s[i];
//			s[i] = s[j];
//			s[j] = temp;
//		}
//	}
//	//基准数归位
//	s[low] = s[i];
//	s[i] = base;
//	Quick_Sort(s, low, i - 1);//递归左子表
//	Quick_Sort(s, i + 1, high);//递归右子表
//}

int Is_Pivot(vector<int>& nums, int low, int high);

void Quick_sort(vector<int>& nums, int low, int high)
{
    while (low < high)
    {
        int pivot = Is_Pivot(nums, low, high);
        Quick_sort(nums, low, pivot - 1);
        low = pivot + 1;
    }
}

int Is_Pivot(vector<int>& nums, int low, int high)
{
    int temp = nums[low];//哨兵
    while (low < high)
    {
        while (nums[high] >= temp && low < high) high--;
        nums[low] = nums[high];
        while (nums[low] <= temp && low < high)  low++;
        nums[high] = nums[low];
    }
    nums[low] = temp;
    return low;//返回枢轴点下标
}

int main()
{
	vector<int>v{ 1,10,24,12,16,8 };
	Quick_sort(v,0,5);
	cout<<"排序从小到大:" << endl;
	//cout << "排序从大到小:" << endl;
	for(vector<int>::iterator it=v.begin(); it!=v.end();it++)
	{
		cout << *it << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

六、堆排序

//堆排序
//不稳定
//时间复杂度:O(nlog(n))
//空间复杂度:O(1)
#include <iostream>
#include <vector>
using namespace std;

void heapadjust(vector<int>& nums, int i, int maxsize);

int heapsort(vector<int>& nums, int k)
{
    int n = nums.size();
    for (int i = n / 2; i >= 1; i--)
    {
        heapadjust(nums, i, n);
    }
    int size = n;
    for (int i = 1; i < k; i++)
    {
        swap(nums[0], nums[size - 1]);
        heapadjust(nums, 1, size - 1);
        size--;
    }
    return nums[0];
}

//堆的建立
void heapadjust(vector<int>& nums, int i, int maxsize)
{
    int temp = nums[i - 1];
    for (int j = i * 2; j <= maxsize; j *= 2)
    {
        if (j + 1 <= maxsize && nums[j] > nums[j - 1])
        {
            j++;
        }
        if (temp > nums[j - 1])break;
        nums[i - 1] = nums[j - 1];
        i = j;
    }
    nums[i - 1] = temp;
}

//调整大顶堆,升序输入
//n数组长度,i待调整节点,数组下标从0开始
void heapify(vector<int>& nums,int n,int i)
{
    int largest = i, lson = 2 * i + 1, rson = 2 * i + 2;
    if (lson < n && nums[largest] < nums[lson])
    {
        largest = lson;
    }
    if (rson < n && nums[largest] < nums[rson])
    {
        largest = rson;
    }
    if (largest != i)
    {
        swap(nums[largest], nums[i]);
        heapify(nums, n, largest);//递归待调整节点largest
    }

}

void heap_Sort(vector<int>& nums, int n)
{
    //建堆
    for (int i = n / 2 - 1; i >= 0; i--)
    {
        heapify(nums, n, i);//调整节点n/2-1
    }
    //排序
    for (int i = n - 1; i >= 0; i--)
    {
        swap(nums[i], nums[0]);
        heapify(nums, i, 0);//调整堆顶节点
    }
}

int main()
{
    vector<int>v{ 1,10,24,12,16,8 };
    heap_Sort(v, 6);
    for(vector<int>::iterator it=v.begin(); it!=v.end();it++)
	{
		cout << *it << " ";
	}
    system("pause");
    return 0;
}

七、归并排序

将⼀个⼤的⽆序数组有序,我们可以把⼤的数组分成两个,然后对这两个数组分别进⾏排序,之后在把这两个数组合并成⼀个有序的数组。由于两个⼩的数组都是有序的,所以在合并的时候是很快的。
通过递归的⽅式将⼤的数组⼀直分割,直到数组的⼤⼩为 1,此时只有⼀个元素,那么该数组就是有序的了,之后再把两个数组⼤⼩为1的合并成⼀个⼤⼩为2的,再把两个⼤⼩为2的合并成4的 … 直到全部⼩的数组合并起来。

归并排序是建⽴在归并操作上的⼀种有效的排序算法。该算法是采⽤分治法(Divide and Conquer)的⼀个⾮常典型的应⽤。将已有序的⼦序列合并,得到完全有序的序列;即先使每个⼦序列有序,再使⼦序列段间有序。若将两个有序表合并成⼀个有序表,称为2-路归并

算法思想
1、把⻓度为n的输⼊序列分成两个⻓度为n/2的⼦序列;
2、对这两个⼦序列分别采⽤归并排序;
3、 将两个排序好的⼦序列合并成⼀个最终的排序序列。

void mergeSortCore(vector<int>& data, vector<int>& dataTemp, int low, int high) {
 if (low >= high) return;
 int len = high - low, mid = low + len / 2;
 int start1 = low, end1 = mid, start2 = mid + 1, end2 = high;
 mergeSortCore(data, dataTemp, start1, end1);
 mergeSortCore(data, dataTemp, start2, end2);
 int index = low;
 while (start1 <= end1 && start2 <= end2) {
 dataTemp[index++] = data[start1] < data[start2] ? data[start1++] : data[start2++];
 }
 while (start1 <= end1) {
 dataTemp[index++] = data[start1++];
 }
 while (start2 <= end2) {
 dataTemp[index++] = data[start2++];
 }
 for (index = low; index <= high; ++index) {
 data[index] = dataTemp[index];
 }
}
void mergeSort(vector<int>& data) {
 int len = data.size();
 vector<int> dataTemp(len, 0);
 mergeSortCore(data, dataTemp, 0, len - 1);
}

八、计数排序

计数排序统计⼩于等于该元素值的元素的个数i,于是该元素就放在⽬标数组的索引i位(i≥0)。计数排序基于⼀个假设,待排序数列的所有数均为整数,且出现在(0,k)的区间之内。如果 k(待排数组的最⼤值) 过⼤则会引起较⼤的空间复杂度,⼀般是⽤来排序 0 到 100 之间的数字的最好的算法,但是它不适合按字⺟顺序排序⼈名。计数排序不是比较排序,排序的速度快于任何比较排序算法

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 计数排序
void CountSort(vector<int>& vecRaw, vector<int>& vecObj)
{
 // 确保待排序容器⾮空
 if (vecRaw.size() == 0)
 return;
 // 使⽤ vecRaw 的最⼤值 + 1 作为计数容器 countVec 的⼤⼩
 int vecCountLength = (*max_element(begin(vecRaw), end(vecRaw))) + 1;
 vector<int> vecCount(vecCountLength, 0);
 // 统计每个键值出现的次数
  for (int i = 0; i < vecRaw.size(); i++)
 vecCount[vecRaw[i]]++;
 
 // 后⾯的键值出现的位置为前⾯所有键值出现的次数之和
 for (int i = 1; i < vecCountLength; i++)
 vecCount[i] += vecCount[i - 1];
 // 将键值放到⽬标位置
 for (int i = vecRaw.size(); i > 0; i--) // 此处逆序是为了保持相同键值的稳定性
 vecObj[--vecCount[vecRaw[i - 1]]] = vecRaw[i - 1];
}
int main()
{
 vector<int> vecRaw = { 0,5,7,9,6,3,4,5,2,8,6,9,2,1 };
 vector<int> vecObj(vecRaw.size(), 0);
 CountSort(vecRaw, vecObj);
 for (int i = 0; i < vecObj.size(); ++i)
 cout << vecObj[i] << " ";
 cout << endl;
 return 0;
}

九、桶排序

将值为i的元素放⼊i号桶,最后依次把桶⾥的元素倒出来。
算法思想:

  1. 设置⼀个定量的数组当作空桶⼦。
  2. 寻访序列,并且把项⽬⼀个⼀个放到对应的桶⼦去。
  3. 对每个不是空的桶⼦进⾏排序。
  4. 从不是空的桶⼦⾥把项⽬再放回原来的序列中
function bucketSort(arr, bucketSize) {
 if (arr.length === 0) {
 return arr;
 }
 var i;
 var minValue = arr[0];
 var maxValue = arr[0];
 for (i = 1; i < arr.length; i++) {
 if (arr[i] < minValue) {
 minValue = arr[i]; // 输⼊数据的最⼩值
 } else if (arr[i] > maxValue) {
 maxValue = arr[i]; // 输⼊数据的最⼤值
 }
 }
 // 桶的初始化
 var DEFAULT_BUCKET_SIZE = 5; // 设置桶的默认数ᰁ为5
 bucketSize = bucketSize || DEFAULT_BUCKET_SIZE;
 var bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1;
 var buckets = new Array(bucketCount);
 for (i = 0; i < buckets.length; i++) {
 buckets[i] = [];
 }
 // 利⽤映射函数将数据分配到各个桶中
 for (i = 0; i < arr.length; i++) {
 buckets[Math.floor((arr[i] - minValue) / bucketSize)].push(arr[i]);
 }
 arr.length = 0;
 for (i = 0; i < buckets.length; i++) {
 insertionSort(buckets[i]); // 对每个桶进⾏排序,这⾥使⽤了插⼊排for (var j = 0; j < buckets[i].length; j++) {
 arr.push(buckets[i][j]);
 }
 }
 return arr;
}

十、基数排序

⼀种多关键字的排序算法,可⽤桶排序实现。
算法思想:

  1. 取得数组中的最⼤数,并取得位数;
  2. arr为原始数组,从最低位开始取每个位组成radix数组;
  3. 对radix进⾏计数排序(利⽤计数排序适⽤于⼩范围数的特点)
int maxbit(int data[], int n) //ᬀ助函数,求数据的最⼤位数
{
 int maxData = data[0]; ///< 最⼤数
 /// 先求出最⼤数,再求其位数,这样有原先依次每个数判断其位数,稍微优化点。
 for (int i = 1; i < n; ++i)
 {
 if (maxData < data[i])
 maxData = data[i];
 
 int d = 1;
 int p = 10;
 while (maxData >= p)
 {
 //p *= 10; // Maybe overflow
 maxData /= 10;
 ++d;
 }
 return d;
/* int d = 1; //保存最⼤的位数
 int p = 10;
 for(int i = 0; i < n; ++i)
 {
 while(data[i] >= p)
 {
 p *= 10;
 ++d;
 }
 }
 return d;*/
}
void radixsort(int data[], int n) //基数排序
{
 int d = maxbit(data, n);
 int *tmp = new int[n];
 int *count = new int[10]; //计数器
 int i, j, k;
 int radix = 1;
 for(i = 1; i <= d; i++) //进⾏d次排序
 {
 for(j = 0; j < 10; j++)
 count[j] = 0; //每次分配前清空计数器
 for(j = 0; j < n; j++)
 {
 k = (data[j] / radix) % 10; //统计每个桶中的记录数
 count[k]++;
 }
 for(j = 1; j < 10; j++)
 count[j] = count[j - 1] + count[j]; //将tmp中的位置依次分配给每个桶
 for(j = n - 1; j >= 0; j--) //将所有桶中记录依次收集到tmp中
 {
 k = (data[j] / radix) % 10;
 tmp[count[k] - 1] = data[j];
 count[k]--;
 }
 for(j = 0; j < n; j++) //将临时数组的内容复制到data中
 data[j] = tmp[j];
 radix = radix * 10;
 }
 delete []tmp;
 delete []count;
}

总结

以上就是本人学习排序算法过程中的一些总结,部分注释部分是不同的写法,可自由切换,都经过测试,均可跑通!

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值