排序算法总结

目录

一、冒泡排序

二、快速排序

三、插入排序

四、堆排序【此处介绍大根堆】

五、归并排序

六、希尔排序

七、简单选择排序

八、基数排序


算法名称时间复杂度应用场景
冒泡排序O(n^2)在最好的情况下,也就是数列本身是排好序的,需要进行 n - 1 次比较
快速排序O(nlogn)一种不稳定的排序算法
插入排序O(n^2)最稳定的排序方法
堆排序O(nlogn)不适用于序列个数较少的情况
归并排序O(nlogn)是一种效率高且稳定的算法
希尔排序O(n^(3/2))时间复杂度相对O(n^2)较低
简单选择排序O(n^2)性能上略优于冒泡排序
基数排序O(KN)在某些时候,基数排序法的效率高于其它的稳定性排序法

一、冒泡排序

冒泡排序的原理:每一趟只能确定将一个数归位。即第一趟只能确定将末位上的数归位,第二趟只能将倒数第 2 位上的数归位,依次类推下去。如果有 n 个数进行排序,只需将 n-1 个数归位,也就是要进行 n-1 趟操作。

而 “每一趟 ” 都需要从第一位开始进行相邻的两个数的比较,将较大的数放后面,比较完毕之后向后挪一位继续比较下面两个相邻的两个数大小关系,重复此步骤,直到最后一个还没归位的数。

#include<iostream>
#include<vector>
using namespace std;
/*冒泡排序*/
class bubbleSort {
private:
	vector<int> nums;
public:
	bubbleSort(vector<int> temp) :nums(temp) {};
	void sort();
	void show();
};
//排序
void bubbleSort::sort()
{
	for (int i = 0; i < nums.size(); i++)
	{
		for (int j = 1; j < nums.size() - i; j++)
		{
			if (nums[j - 1] > nums[j])
				swap(nums[j - 1], nums[j]);
		}
	}
}
//打印排序后的数组
void bubbleSort::show()
{
	for (int i = 0; i < nums.size(); i++)
		cout << nums[i] << " ";
	cout << endl;
}

二、快速排序

快速排序的原理

单趟排序的思路:取区间中最左(或最右边)的元素为key,定义两个变量,这里假设是p和q,q从区间的最右边向左走,找到比key小的元素就停下。p从最左边向右走,找到比key大的元素就停下。然后交换p和q所指向的元素

重复上面的过程,直到pq相遇,交换key和pq相遇位置的元素。

将key左右两边的区间分别进行上述单趟排序,并以此规律进行递归,截取更小区间进行单趟排序,最终当区间长度为1时,退出当前层,回溯,排序结束。

#include<iostream>
#include<vector>
using namespace std;
/*快速排序*/
class fastSort {
private:
	vector<int> nums;
public:
	fastSort(vector<int>temp) :nums(temp) {};
	void sort(int start, int end);
	void show();
};

void fastSort::sort(int start,int end)
{
	if (end - start <= 0)
		return;
	int left = start;//【注意!!!】此处不是left=start+1; 而是left=start;
	int right = end;
	while (right > left)
	{
		//如果每次比较的值取的是最左边的第一个数,那么在进行比较分配左右子数组时,应该是右数组先开始,反之亦然。
		while (nums[right] > nums[start] && right > left)
			right--;
		while (nums[left] <= nums[start] && right > left)
			left++;
		if (left == right)
		{
			//当left和right重合时,将最左边第一个数与重合点进行交换
			swap(nums[start], nums[left]);
			break;
		}
		else
		{   //当右边出现小于左边第一个数的元素,左边出现大于左边第一个元素的数,则交换这两个数的位置。
			swap(nums[left], nums[right]);
		}
	}
    //左递归区间,单趟排序
	sort(start, left - 1);
    //左递归区间,单趟排序
	sort(left + 1, end);
}

//打印排序后的数组
void fastSort::show()
{
	for (int i = 0; i < nums.size(); i++)
		cout << nums[i] << " ";
	cout << endl;
}

三、插入排序

快速排序的原理

从第二个数开始遍历,for(int cur=1;cur<nums.size();cur++),当遍历到当前元素nums[cur]时,倒序向前[0:cur]区间内取遍历寻找,寻找到一个小于nums[cur]的元素索引insert,将nums[cur]插入到insert+1位置。

注意:此处是插入,原[index+1:cur-1]区间的元素都向后移一位,为index提供空间,不是交换。

#include<iostream>
#include<vector>
using namespace std;
/*插入排序*/
class insertSort {
private:
	vector<int> nums;
public:
	insertSort(vector<int> temp) :nums(temp) {};
	void sort();
	void makRoom(int index, int num);
	void show();
};
void insertSort::sort()
{
	//从第二个元素开始,第一个元素就认为是已经排好的数组
	for (int i = 1; i < nums.size(); i++)
	{
		for (int j = i - 1; j >= 0; j--)
		{
			//寻找排列好的数组中从大到小的第一个小于num[i]的元素nums[j],将nums[i]插入到j+1位置
			if (nums[i] >= nums[j])
			{
				makRoom(j + 1, i);
				break;//【注意!!!】别忘了这个break;
			}
			//当nums[i]小于所有排列好的数组元素时,将其插入位置0
			else if (j == 0)
				makRoom(0, i);
		}
	}
}
//将索引为num的元素插入到索引index处
void insertSort::makRoom(int index, int num)
{
	int temp = nums[num];
	for (int i = num; i >= index+1; i--)
	{
		nums[i] = nums[i - 1];
	}
	nums[index] = temp;
}
//打印排序后的数组
void insertSort::show()
{
	for (int i = 0; i < nums.size(); i++)
		cout << nums[i] << " ";
	cout << endl;
}

四、堆排序【此处介绍大根堆】

大根堆概念:在树中除叶子节点外,所有根节点的值都大于其叶子节点。

完全二叉树相关概念:

  • 不管是否为满完全二叉树,只要是完全二叉树,则其最后一个非叶子节点的在对应数组中的索引 = 树节点总量length/2。{此处根节点在数组中的索引为1,不是0}。
  • 完全二叉树中父节点索引为i   ---->  左子节点索引为2*i,右子节点索引为2*i+1;
    完全二叉树中子节点索引为i   ---->  父节点索引为i/2;

无序序列如图:

无序完全二叉树:

第一步,首先针对给出待排序数组,初始化构造一个大根堆。从最后一个非叶子节点开始,以倒序层序遍历的方式往根节点移动保证移动过程中,经过的每个节点,以其为根节点的子树都满足大根堆。

(a)最后一个非叶子节点,满足以其为根节点的树为大根堆 

图(a)

(b)倒数第二个非叶子节点,以其为根节点的子树,不满足大根堆的要求,因此需要将其与左右节点中较大的节点值进行交换。

 图(b)

(c)倒数第三个非叶子节点,以其为根节点的子树,不满足大根堆的要求,因此需要将其与左右节点中较大的节点值进行交换。

 

(d)倒数第四个非叶子节点,以其为根节点的子树,不满足大根堆的要求,因此需要将其与左右节点中较大的节点值进行交换。但是,此时可以看到交换后的节点,其被交换的子节点处,不再满足大根堆的要求。

因此每次实际产生交换后,需要对其被交换的子节点进行大根堆判断,如果不是大根堆,则需要调整子节点与其下一代子节点的值,调整为大根堆。

 

(e)倒数第五个非叶子节点同上理。

 经过上述五条,即可将原来的无序数组,调整为一个大根堆,此时其根节点的值为全局最大值。

/*
 * 初始化整个数组元素构造成大根堆
 * 前提知识:当一棵树有len个节点,根节点索引为1,则其最后一个非叶子节点在数组中的索引为len/2
 */
void maxHeapSort::maxHeap()
{
	//获得当前树的最后一个非叶子节点
	int k = length / 2;
	//从最后一个非叶子节点索引开始层序遍历倒序往根节点移,保证以节点i为根节点的子树都满足大根堆
	for (int i = length / 2; i >= 1; i--)
	{
		heapAdjust(i, length);
	}
}
/*
 * 从节点k开始,构造以节点k位根节点的子树为大根堆
 */
void maxHeapSort::heapAdjust(int k,int len)
{
	int temp = 0;
	for (int i = 2 * k; i <= len; i *= 2)
	{
		//找到节点k左右子节点中较大的那个
		if (i < len && array[i] < array[i + 1])
		{
			i++;
		}
		//比较当前节点与其两个叶子节点最大的那个元素之间的大小关系
		//如果当前节点>叶子节点的最大值,直接跳出循环
		//如果当前节点<叶子节点的最大值,则交换当前节点与叶子节点值,
		if (array[k] < array[i])
		{
			temp = array[k];
			array[k] = array[i];
			array[i] = temp;
			//【注意】此时由于当前节点与叶子节点的值发生交换,可能会导致被交换的叶子树不再满足大根堆的要求
			//因此需要将当前节点的索引移动到被交换的叶子索引处,继续向下检查每个节点的值是否都大于其两个叶子子节点
			k = i;
		}
		else
			break;
	}
}

第二步,将获得的大根堆,根节点与其数组末尾元素进行交换,此时获得排序中的第一个数。由于此时大根堆中根节点不满足大根堆条件,则调整根节点与其子节点的值,验证被调整的子节点,向下深挖。即调用heapAdjust(1,len-1)。

第三步,将获得的大根堆,根节点与其数组末尾倒数第二个元素进行交换,此时获得排序中的第一个数。由于此时大根堆中根节点不满足大根堆条件,则调整根节点与其子节点的值,验证被调整的子节点,向下深挖。即调用heapAdjust(1,len-2)。

完整代码如下:

#include <iostream>
#include <vector>
using namespace std;
/*堆排序*/
class maxHeapSort {
private:
	vector<int> array;
	int length;
public:
	maxHeapSort(vector<int> nums) :array(nums), length(nums.size()-1) {};
	void heapAdjust(int k,int len);
	void maxHeap();
	void heapSort();
	void show();
};
/*
 * 从节点k开始,构造以节点k位根节点的子树为大根堆
 */
void maxHeapSort::heapAdjust(int k,int len)
{
	int temp = 0;
	for (int i = 2 * k; i <= len; i *= 2)
	{
		//找到节点k左右子节点中较大的那个
		if (i < len && array[i] < array[i + 1])
		{
			i++;
		}
		//比较当前节点与其两个叶子节点最大的那个元素之间的大小关系
		//如果当前节点>叶子节点的最大值,直接跳出循环
		//如果当前节点<叶子节点的最大值,则交换当前节点与叶子节点值,
		if (array[k] < array[i])
		{
			temp = array[k];
			array[k] = array[i];
			array[i] = temp;
			//【注意】此时由于当前节点与叶子节点的值发生交换,可能会导致被交换的叶子树不再满足大根堆的要求
			//因此需要将当前节点的索引移动到被交换的叶子索引处,继续向下检查每个节点的值是否都大于其两个叶子子节点
			k = i;
		}
		else
			break;
	}
}
/*
 * 初始化整个数组元素构造成大根堆
 * 前提知识:当一棵树有len个节点,根节点索引为1,则其最后一个非叶子节点在数组中的索引为len/2
 */
void maxHeapSort::maxHeap()
{
	//获得当前树的最后一个非叶子节点
	int k = length / 2;
	//从最后一个非叶子节点索引开始层序遍历倒序往根节点移,保证以节点i为根节点的子树都满足大根堆
	for (int i = length / 2; i >= 1; i--)
	{
		heapAdjust(i, length);
	}
}
void maxHeapSort::heapSort()
{
	//将需要排列顺序的数组构造成一个大根堆
	maxHeap();

	//首先需要保证以nums[1]为根节点的树变为大根堆
	for (int i = array.size() - 1; i >= 1; i--)
	{
		heapAdjust(1,i);
		//对于数组长度为len的大根堆,将其num[1]与数组最后一个元素交换后,用于构造大根堆的数组缩短一个元素
		//最大元素被保存在原数组的nums[i]中
		int temp = array[1];
		array[1] = array[i];
		array[i] = temp;
	}

}
//打印排序后的数组
void maxHeapSort::show()
{
	for (int i = 0; i < array.size(); i++)
	cout << array[i] << "  ";
	cout << endl;
}

五、归并排序

快速排序的原理

采用递归方法,将整个数组进行不断划分,直到划分的每个字数组的长度为0或者为1,这时每个子数组都是有序数组,这是再按照有序数组的拼接算法,对每个子数组进行拼接,这样就能保证每次的拼接结果都还是有序的最终拼接成一个之后,整个数组便都是有序的,而数组的排序也宣布完成。

#include <iostream>
#include <vector>
using namespace std;
class mergeSort {
private:
	vector<int> array;
	int length;
public:
	mergeSort(vector<int> nums) :array(nums), length(nums.size()) {};
	~mergeSort() {};
	vector<int> mergeArray(vector<int>& nums1, vector<int>& nums2);
	void sort();
	void show();

};
//将数组拆分成等分,递归,并在回溯期间进行有序数组的归并
vector<int> mergeSort::mergeArray(vector<int>& nums1, vector<int>& nums2)
{
	//递归退出条件,当需要合并的两个数组中,其中一个数组为空,则返回另一个数组
	if (nums1.size() == 0 || nums2.size() == 0)
		return nums1.size() ? nums1 : nums2;
	//将每个传进的来的数组都分割成两个数组继续递归
	int mid1 = nums1.size() / 2;
	vector<int> nums11 = vector<int>(nums1.begin(), nums1.begin() + mid1);
	vector<int> nums12 = vector<int>(nums1.begin() + mid1, nums1.end());
	int mid2 = nums2.size() / 2;
	vector<int> nums21 = vector<int>(nums2.begin(), nums2.begin() + mid2);
	vector<int> nums22 = vector<int>(nums2.begin() + mid2, nums2.end());

	nums1 = mergeArray(nums11, nums12);
	nums2 = mergeArray(nums21, nums22);
	//在每层递归,将两个有序数组中的元素进行归并,合成一个数组后返回
	vector<int> temp(nums1.size()+nums2.size(),0);
	int index = 0;
	int index1 = 0;
	int index2 = 0;
	while (index1 < nums1.size() || index2 < nums2.size())
	{
		if (index1<nums1.size()&&index2<nums2.size())
		{
			if(nums1[index1] > nums2[index2])
			temp[index++] = nums2[index2++];
			else
			temp[index++] = nums1[index1++];
		}
		else if (index2 < nums2.size())
			temp[index++] = nums2[index2++];
		else if (index1 < nums1.size())
			temp[index++] = nums1[index1++];
	}
	//返回两两有序数组的合成数组
	return temp;
}
//将原数组拆分成两等份,进行递归,回溯时归并
void mergeSort::sort()
{
	int mid = array.size() / 2;
	vector<int> nums1 = vector<int>(array.begin(), array.begin() + mid);
	vector<int> nums2 = vector<int>(array.begin() + mid, array.end());
	array = mergeArray(nums1, nums2);
}

//打印排序后的数组
void mergeSort::show()
{
	for (int i = 0; i < array.size(); i++)
		cout << array[i] << " ";
	cout << endl;
}

六、希尔排序

希尔排序原理:

  • 首先将数组分为s = length/2份,对每一份进行插入排序;
  • 再将数组分为s=s/2份,对每一份进行插入排序;
  • ...,,直至将数组分成s = 1份,对该份进行插入排序;

代码中需要注意:每次不是将分出来的每份进行单独的插入排序,而是每次都是挨个元素遍历,在插入排序时,对当前所在组按照步长进行移动,具体可看代码如下:

#include <iostream>
#include <vector>
using namespace std;
/*希尔排序*/
class shellSort {
private:
	vector<int> array;
	int length;
public:
	shellSort(vector<int> nums) :array(nums), length(nums.size()) {};
	void makeRoom(int i, int j, int s);
	void sort();
	void show();
};
void shellSort::sort()
{
	//将原始数组分成sp份
	int sp = length / 2;
	for (int s = sp; s >= 1; s /= 2)
	{
		cout <<"s: " << s << endl;
		//首先要跳过每份的第1个元素
		for (int i = s; i < length; i++)
		{
			//此处需要注意的是每次比较的步长为s
			for (int j = i - s; j >= 0; j-=s)
			{
				if (array[i] >= array[j])
				{
					//将索引i的元素填充到索引j+s,即元素j所属份的下一步的索引处
					makeRoom(j+s, i, s);
					break;//【注意:别忘了这个break】
				}
				else if(j==0)
					makeRoom(0, i ,s);
			}
		}
		show();
	}
}
//将索引为num的元素插入到索引index处
//记住,位移的步长为s
void shellSort::makeRoom(int index, int num, int s)
{
	int temp = array[num];
	if (num > index)
	{
		for (int i = num; i >= (index + s); i -= s)
		{
			array[i] = array[i - s];
		}
		array[index] = temp;
	}
}
//打印排序后的数组
void shellSort::show()
{
	for (int i = 0; i < array.size(); i++)
		cout << array[i] << " ";
	cout << endl;
}

七、简单选择排序

简单选择排序原理:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。

#include<iostream>
#include<vector>
using namespace std;
/*简单选择排序*/
class simpleSelectionSort {
private:
	vector<int> nums;
public:
	simpleSelectionSort(vector<int> temp) :nums(temp){};

	void sort();
	void show();
};
//每一次从未排序的部分中选取最小的元素,放到已排序部分最尾
//采用的是swap(已排序部分尾部+1,未排序部分中最小元素)的方式来放置
void simpleSelectionSort::sort()
{
	for (int i = 0; i < nums.size(); i++)
	{
		int Min = nums[i];
		int index = i;
		for (int j = i; j < nums.size(); j++)
		{
			if (Min > nums[j])
			{
				Min = nums[j];
				index = j;
			}	
		}
		//swap(已排序部分尾部 + 1,未排序部分中最小元素)
		swap(nums[i], nums[index]);
	}
}
//打印排序后的数组
void simpleSelectionSort::show()
{
	for (int i = 0; i < nums.size(); i++)
		cout << nums[i] << " ";
	cout << endl;
}

八、基数排序

基数排序原理:将所有待比较数值(自然数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

实现步骤:

  • 确定数组中的最大元素有几位(MAX)(确定执行的轮数);
  • 创建0~9个桶(桶的底层是队列),因为所有的数字元素都是由0~9的十个数字组成;
  • 依次判断每个元素的个位,十位至MAX位,存入对应的桶中,出队,存入原数组;直         至MAX轮结束输出数组;
  • 具体实现步骤如下图:

#include<iostream>
#include<vector>
#include<queue>
using namespace std;
/*基数排序*/
class radixSort
{
private:
	struct ListNode
	{
		int val;
		ListNode* next;
		ListNode() :val(0), next(nullptr) {};
		ListNode(int value) :val(value), next(nullptr) {};
		ListNode(int value, ListNode* ptr) :val(value), next(ptr) {};
	};
	vector<ListNode*> array;
	vector<int> nums;
public:
	radixSort(vector<int> temp);
	int getIndex(int num, int step);
	void sort();
	void show();
	int maxStep();
	//void sort(int* begin, int* end);
};
//构造函数,初始化桶数组为一个含有10个链表元素的数组
radixSort::radixSort(vector<int> temp)
{
	array = vector<ListNode*>(10);
	nums = temp;
}
//获得数字num第step位的数字大小,如果不存在,则直接返回0
int radixSort::getIndex(int num, int step)
{
	int result = 0;
	while (step != 0)
	{
		num = num / 10;
		step--;
		if (num == 0)
			return 0;
	}
	result = num % 10;
	return result;
}
//将待排序数组中的元素按照第step位依次放到桶中,并挨个桶取出返回新的数组
void radixSort::sort()
{
	int step = 0;
	//依次进行个位、十位、百位... ...的基数排序
	while (step< maxStep())
	{
		vector<int> result;
		auto start = nums.begin();
		while (start != nums.end())
		{
			//将元素*start包装成一个链表节点
			ListNode* node = new ListNode(*start);

			//计算该元素在第step位的索引
			int index = getIndex(*start, step);

			//将包装好的节点查到数组对应索引的链表中,尾插法
			if (array[index] == nullptr)
				array[index] = node;
			else
			{
				ListNode* temp = array[index];
				while (temp != nullptr)
				{
					if (temp->next == nullptr)
					{
						temp->next = node;
						break;
					}
					temp = temp->next;
				}
			}
			start++;
		}
		//将数组中对应索引链表中的元素重新放入一个新的数组result中返回
		for (int i = 0; i < array.size(); i++)
		{
			while (array[i] != nullptr)
			{
				result.push_back(array[i]->val);
				array[i] = array[i]->next;
			}
		}
		nums = result;
		step++;
	}
}
//获取待排序数组中最大元素的位数
int radixSort::maxStep()
{
	int step = 0;
	int Max = 0;
	for (int i = 0; i < nums.size(); i++)
	{
		int temp = nums[i];
		while (temp != 0)
		{
			temp = temp / 10;
			step++;
		}
		Max = max(Max, step);
	}
	return Max;
}
//打印排序后的数组
void radixSort::show()
{
	for (int i = 0; i < nums.size(); i++)
		cout << nums[i] << " ";
	cout << endl;
}

所有排序算法源码汇总https://github.com/OYWALT/SortFunction

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值