【C++】八大排序

news2024/12/23 14:42:57

文章目录

    • 前言
    • 1. 插入排序
    • 2. 希尔排序
    • 3. 选择排序
    • 4. 堆排序
    • 5. 冒泡排序
    • 6. 快速排序(重点)
      • 6.1 快速排序(hoare版本)
      • 6.2 快速排序(挖坑法)
      • 6.3 快速排序(前后指针法)
      • 6.4 快速排序(非递归)
      • 6.5 快速排序(优化)
    • 7. 归并排序
      • 7.1 归并排序(递归实现)
      • 7.2 归并排序非递归实现
    • 8. 计数排序
    • 排序算法复杂度及稳定性分析
    • 代码
      • Sort类实现代码
      • 测试代码

前言

此篇博客使用C++语言来实现排序讲解,以下是一个Sort类,我将把所有的排序算法封装成静态成员函数函数放到这个类中。我会将所有实现代码和测试代码放在文章最后

template<class T>
struct my_less {   // 升序
	bool operator()(const T& a, const T& b) { return a > b; }
};

template<class T>
struct my_greater { // 降序
	bool operator()(const T& a, const T& b) { return a < b; }
};
template<class T, class C = my_less<T>>  //默认升序
class Sort{
public:
	static C comp;	//仿函数类对象,用于控制升序或者降序
	static void mySwap(T& a, T& b) { T tmp = a; a = b; b = tmp; } //交换两个元素
	static void InsertSort(vector<T>& v);		// 直接插入排序
	static void ShellSort(vector<T>& v);		// 希尔排序
	static void SelectSort(vector<T>& v);		// 选择排序
	static void BubbleSort(vector<T>& v);		// 冒泡排序
	static void HeapSort(vector<T>& v);			// 堆排序
	static void MergeSort(vector<T>& v);		// 归并排序(递归)
	static void nonreMergeSort(vector<T>& v);   // 归并排序(非递归)

	//快速排序
	static void hoareQuickSort(vector<T>& v);   // hoare版快速排序(递归)
	static void digpitQuickSort(vector<T>& v);  // 挖坑版快速排序(递归)
	static void froaftQuickSort(vector<T>& v);  // 前后指针版快速排序(递归)
	static void nonreQuickSort(vector<T>& v);   // 快速排序(非递归)
	static int midinTree(vector<T>& v, int left, int right); // 三数取中(优化)

	//递归子函数
	static void _MergeSort(vector<T>& v, vector<T>& tmp, int left, int right);
	static void _hoareQuickSort(vector<T>& v, int begin, int end);
	static void _digpitQuickSort(vector<T>& v, int begin, int end);
	static void _froaftQuickSort(vector<T>& v, int left, int right);
};

template<class T, class C>
C Sort<T, C>::comp;

1. 插入排序

基本思想
直接插入排序是一种简单的插入排序法,:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。

实现代码

template<class T, class C>
void  Sort<T, C>::InsertSort(vector<T>& v) {
	for (int i = 0; i < v.size(); i++) { // i 表示准备插入数据的下标
		if (i == 0) continue;
		int temp = v[i], cur = i - 1;    // temp表示准备插入的数据 cur为对比的数据下标
		while (cur >= 0) {               // 当cur < 0 时说明没有数据比temp大,则退出
			if (comp(temp, v[cur])) {    // 默认是降序的情况下,若temp > v[cur]则进入
				v[cur + 1] = v[cur];     // 将cur 位置的数据移动至cur + 1
				cur--;                   // 向前寻找插入位置
			}
			else {                       // 说明前面[0, cur]数据均大于temp,[cur + 1, i]位置均小于temp
				break;
			}
		}
		v[cur + 1] = temp;               // 在cur + 1位置插入temp      
	}
}

测试代码

#include "Sort.h"

void printVector(vector<int>& v) {
	for (const auto& el : v) {
		cout << el << " ";
	}
	cout << endl;
}

int main() {
	vector<int> v1;
	vector<int> v2{ 3, 2, 5, 1 ,4 };
	vector<int> v3;
	for (int i = 0; i < 10; i++) {
		v3.push_back(rand() % 100);
	}
	
	Sort<int>::InsertSort(v1);
	Sort<int>::InsertSort(v2);
	Sort<int>::InsertSort(v3);
	printVector(v1);
	printVector(v2);
	printVector(v3);
	cout << endl;
	return 0;
}

直接插入排序的特性总结:

  • 元素集合越接近有序,直接插入排序算法的时间效率越高
  • 时间复杂度:O(N^2)
  • 空间复杂度:O(1),它是一种稳定的排序算法
  • 稳定性:稳定

2. 希尔排序

基本思想:
希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当组距=1时,所有记录在统一组内排好序。
实现代码

template<class T, class C> 
void Sort<T, C>::ShellSort(vector<T>& v) {
	int gap = v.size();                 // 步长
	while (gap > 1) {                   // 当 gap == 1时结束,并且数组为空时不会进入
		gap = gap / 3 + 1;              // gap不断减小
		for (int i = 0; i < v.size() - gap; i++) {
			int temp = v[i + gap];      // temp表示准备插入的数据 
			int cur = i;				// cur为对比的数据下标
			while (cur >= 0) {
				if (comp(temp, v[cur])) { // 默认是降序的情况下,若temp > v[cur]则进入
					v[cur + gap] = v[cur];
					cur -= gap;
				}
				else {
					break;				// 说明前面[0, cur]数据均大于temp,[cur + 1, i]位置均小于temp
				}
			}
			v[cur + gap] = temp;		// 在cur + 1位置插入temp    
		}
	}
}

测试代码

void shellSortTest() {
	vector<int> v1;
	vector<int> v2{ 3, 2, 5, 1 ,4 };
	vector<int> v3;
	for (int i = 0; i < 10; i++) {
		v3.push_back(rand() % 100);
	}

	Sort<int>::ShellSort(v1);
	Sort<int>::ShellSort(v2);
	Sort<int>::ShellSort(v3);
	printVector(v1);
	printVector(v2);
	printVector(v3);
	cout << endl;
}

希尔排序的特性总结:

  • 希尔排序是对直接插入排序的优化。
  • 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
  • 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定
    在这里插入图片描述

3. 选择排序

基本思想:
每一次从待排序的数据元素中选出最小和最大的一个元素,存放在序列的起始位置和结束位置,直到全部待排序的数据元素排完。
实现代码:

template<class T, class C>
void Sort<T, C>::SelectSort(vector<T>& v) {
	if (v.size() == 0) return;
	int begin = 0, end = v.size() - 1;
	while (begin < end) {
		int max = begin, min = begin;
		for (int i = begin; i <= end; i++) {
			if (comp(v[i], v[max])) max = i;
			if (comp(v[min], v[i])) min = i;
		}
		mySwap(v[begin], v[max]);
		mySwap(v[end], v[min]);
		begin++;
		end--;
	}
}      

测试代码

void selectSortTest() {
	vector<int> v1;
	vector<int> v2{ 3, 2, 5, 1 ,4 };
	vector<int> v3;
	for (int i = 0; i < 10; i++) {
		v3.push_back(rand() % 100);
	}

	Sort<int>::SelectSort(v1);
	Sort<int>::SelectSort(v2);
	Sort<int>::SelectSort(v3);
	printVector(v1);
	printVector(v2);
	printVector(v3);
	cout << endl;
}

直接选择排序的特性总结:

  • 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  • 时间复杂度:O(N^2)
  • 空间复杂度:O(1)
  • 稳定性:不稳定

4. 堆排序

基本思想:
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
实现代码

template<class T, class C>
void Sort<T, C>::HeapSort(vector<T>& v) {
	if (comp(1, 0)) {
		priority_queue<T, vector<int>, less<int>> heap(v.begin(), v.end()); // 降序使用大堆
		for (int i = 0; i < v.size(); i++) {
			v[i] = heap.top();                         // 堆顶元素最大
			heap.pop();								   // 删除最大元素
		}
	}
	else {
		priority_queue<T, vector<int>, greater<int>> heap(v.begin(), v.end()); // 升序使用小堆
		for (int i = 0; i < v.size(); i++) {
			v[i] = heap.top();
			heap.pop();
		}
	}
}     // 堆排序

测试代码

void heapSortTest() {
	vector<int> v1;
	vector<int> v2{ 3, 2, 5, 1 ,4 };
	vector<int> v3;
	for (int i = 0; i < 10; i++) {
		v3.push_back(rand() % 100);
	}

	Sort<int, my_greater<int>>::HeapSort(v1);
	Sort<int, my_greater<int>>::HeapSort(v2);
	Sort<int, my_greater<int>>::HeapSort(v3);
	printVector(v1);
	printVector(v2);
	printVector(v3);
	cout << endl;
}

堆排序特性

  • 堆排序使用堆来选数,效率就高了很多。
  • 时间复杂度:O(N*logN)
  • 空间复杂度:O(1)
  • 稳定性:不稳定

5. 冒泡排序

template<class T, class C>
void Sort<T, C>::BubbleSort(vector<T>& v) {
	if (v.size() == 0) return;
	for (int i = 0; i < v.size() - 1; i++) {          // 每次选一个数字,需要选v.size() - 1次
		for (int j = 0; j < v.size() - i - 1; j++) {  // 第i次有v.size() - i个数字进行冒泡,需要进行v.size() - 1次比较
			if (comp(v[j + 1], v[j])) {               // 若v[j + 1] > v[j] 则将较小数往后移动
				swap(v[j + 1], v[j]);
			}
		}
	}
}   

6. 快速排序(重点)

基本思想
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

将区间按照基准值划分为左右两半部分的常见方式有:
1.hoare版本
2.挖坑法
3.前后指针版本
上面三种方法我们都会讲解实现一下

快速排序的特性总结:

  • 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
  • 时间复杂度:O(N*logN)
  • 空间复杂度:O(logN)
  • 稳定性:不稳定

6.1 快速排序(hoare版本)

1.选定一个基准值,最好选定最左边或者最右边
2.确定两个指针left 和right 分别从左边和右边向中间遍历数组。
3.如果选最右边为基准值,那么left指针先走,如果遇到大于基准值的数就停下来。
4.然后右边的指针再走,遇到小于基准值的数就停下来。
5.交换left和right指针对应位置的值。
6.重复以上步骤,直到left = right ,最后将基准值与left(right)位置的值交换

为何选右为基准值就要让左指针先走呢,这是因为保证右指针经过的每个值在交换之后都会小于基准值,因为右指针左移的前提是,左指针已经找到比基准值小的值,右指针经过的每个值都会比基准值小,若比基准值大就会停止进行交换。然后又到左指针右移了,这样相交处一定比基准值小。相交左遍一定大于等于基准值,右边一定小于等于基准值

实现代码

template<class T, class C>
void Sort<T, C>::hoareQuickSort(vector<T>& v) {
	if (v.size() == 0) return;
	_hoareQuickSort(v, 0, v.size() - 1);
}   

// 5 3 4 1 2
template<class T, class C>
void Sort<T, C>::_hoareQuickSort(vector<T>& v, int begin, int end) {
	if (begin >= end) return;							
	int mark = v[begin];									// 设最左边的元素为基准值
	int left = begin, right = end;							// 设置边界,注意这里left不能越过基准值,因为若已经有序,右边指针会不断左移至基准值
	while (left < right) {
		while (left < right && !comp(v[right], mark)) {     // 从右开始找到比基准值大的元素, !加仿函数可以表示 >=, <= 等
			right--;
		}
		while (left < right && !comp(mark, v[left])) {		// 从左开始找比基准值小的元素
			left++;
		}
		mySwap(v[left], v[right]);							
	}
	mySwap(v[begin], v[left]);								
	_hoareQuickSort(v, begin, left - 1);					// 此时left左边均大于基准值,对左边进行排序
	_hoareQuickSort(v, left + 1, end);						// 此时right右边均小于基准值,对右边进行排序

测试代码

void hoareQuickSortTest() {
	vector<int> v1;
	vector<int> v2{ 3, 2, 5, 1 ,4 };
	vector<int> v3;
	for (int i = 0; i < 10; i++) {
		v3.push_back(rand() % 100);
	}
	Sort<int>::hoareQuickSort(v1);
	Sort<int>::hoareQuickSort(v2);
	Sort<int>::hoareQuickSort(v3);
	printVector(v1);
	printVector(v2);
	printVector(v3);
	cout << endl;
}

6.2 快速排序(挖坑法)

挖坑法的实现思想与hoare的左右指针法类似。若设置右边为基准值时,让左边先走。因为第一个坑在最右边,是用来寻找最小的元素的

挖坑法和左右指针法不同点在于,左右指针法退出循环时交换基准值数据,而挖坑法是将基准值赋值重复指针处。这是因为
情况1: 已经有序,左右指针移动过程中没有发生数据转移和坑位转移的情况,所以坑位至始至终没有变
情况2:若坑位有变化挖坑法左右指针碰撞处的数据一定是坑,并且坑内数据已经被转移

实现代码

template<class T, class C>
void Sort<T, C>::digpitQuickSort(vector<T>& v) {
	if (v.size() == 0) return;
	_digpitQuickSort(v, 0, v.size() - 1);
}

template<class T, class C>
void Sort<T, C>::_digpitQuickSort(vector<T>& v, int begin, int end) {
	if (begin >= end) return;								// 若没有元素或只有一个元素则已经有序
	int mark = v[end], pit_index = end;						// 设基准值是最右边的元素, 坑的下标为pit_index
	int left = begin; int right = end;	
	while (left < right) {
		while (left < right && !comp(mark, v[left])) {		// 从左边开始找比基准值小的数字,若大于等于基准值则跳过
			left++;
		}
		if (left < right) {									
			v[pit_index] = v[left];							// 找到比基准值小的数字,则将小的数字填充到坑位中	
			pit_index = left;								// 将现位置更新为新的坑
		}
		while (left < right && !comp(v[right], mark)) {		// 从右边开始找比基准值大的数字,若小于等于基准值则跳过
			right--;
		}
		if (left < right) {
			v[pit_index] = v[right];						// 找到比基准值小的数字,则将大的数字填充到坑位中		
			pit_index = right;								// 更新坑位
		}
	}
	v[left] = mark;											// 左右指针相遇,将此处更新为mark基准值
	_digpitQuickSort(v, begin, left - 1);
	_digpitQuickSort(v, left + 1, end);
}

测试代码

void digpitQuickSortTest() {
	vector<int> v1;
	vector<int> v2{ 3, 2, 5, 1 ,4 };
	vector<int> v3;
	for (int i = 0; i < 10; i++) {
		v3.push_back(rand() % 100);
	}
	Sort<int>::digpitQuickSort(v1);
	Sort<int>::digpitQuickSort(v2);
	Sort<int>::digpitQuickSort(v3);
	printVector(v1);
	printVector(v2);
	printVector(v3);
	cout << endl;
}

6.3 快速排序(前后指针法)

template<class T, class C>
void Sort<T, C>::_froaftQuickSort(vector<T>& v, int begin, int end) {
	if (begin >= end) return;
	int prev = begin, cur = begin + 1;        // 定义快指针慢指针
	int mark = v[begin];					  // 定义基准值
	while (cur <= end) {					  // 快指针若超过范围则退出
		if (comp(v[cur], mark) && ++prev != cur) { // 如果快指针指向元素大于基准值,并且prev不指向基准值才可交换
			mySwap(v[cur], v[prev]);		  // 替换快慢指针中的元素					        	
		}
		cur++;								  // 快指针一直往后走	
	}	
	mySwap(v[begin], v[prev]);				  // 将基准值和v[prev]交换 v[prev]一定小于等于基准值	
	_froaftQuickSort(v, begin, prev - 1);     // [begin, prev - 1]元素一定都大于等于基准值
	_froaftQuickSort(v, prev + 1, end);		  // [prev + 1, end] 元素一定小于等于基准值
}

6.4 快速排序(非递归)

快速排序非递归实现,需要借助栈,栈存放的是需要排序的左右边界
而且非递归可以彻底解决栈溢出的问题,递归和非递归的思想是十分类似的。
这里单趟排序使用的是挖坑法(基准值:最左边元素)

template<class T, class C>
void Sort<T, C>::nonreQuickSort(vector<T>& v) {
	if (v.empty()) return;
	stack<int> st;												// 使用栈来记录单趟排序的左右边界
	st.push(v.size() - 1);										// 试着初始边界
	st.push(0);
	while (!st.empty()) {
		int begin = st.top(); st.pop();							// 拿出边界,注意边界插入和拿去顺序是相反的
		int end = st.top();   st.pop();							
		int mark = v[end], pit_index = end;						// 设基准值是最右边的元素, 坑的下标为pit_index
		int left = begin; int right = end;
		while (left < right) {
			while (left < right && !comp(mark, v[left])) {		// 从左边开始找比基准值小的数字,若大于等于基准值则跳过
				left++;
			}
			if (left < right) {
				v[pit_index] = v[left];							// 找到比基准值小的数字,则将小的数字填充到坑位中	
				pit_index = left;								// 将现位置更新为新的坑
			}
			while (left < right && !comp(v[right], mark)) {		// 从右边开始找比基准值大的数字,若小于等于基准值则跳过
				right--;
			}
			if (left < right) {
				v[pit_index] = v[right];						// 找到比基准值小的数字,则将大的数字填充到坑位中		
				pit_index = right;								// 更新坑位
			}
		}
		v[left] = mark;											// 此时[begin, left - 1] 均大于等于基准值[left + 1, end]均小于等于基准值
		if (begin < left - 1) {									// 将左右部分的左右边界放入栈,若左右部分只有一个元素则直接跳过
			st.push(left - 1);					
			st.push(begin);
		}
		if (left + 1 < end) {
			st.push(end);
			st.push(left + 1);
		}
	}
}

6.5 快速排序(优化)

上面就是快速排序递归的三种方法。
但是上面的程序还有一些缺陷:

  • 若基准值最小或最大则递归效率低,至多只能排一个数 (三数取中优化)
  • 在排序大量有序数据或者接近有序数据时,效率会比较低,甚至可能会出现程序崩溃的情况。
    这是因为在排序有序数据时,快速排序的递归调用次数过多,会导致栈溢出的情况。
  • 若快排数组中含有大量的重复元素,效率也会比较低(类似有序),我们可以通过单排过后遍历数组,进行遍历左右部分的元素,将相同元素聚集到中间,重新定义左右边界

为了解决这些问题,这里有两种优化方法:
1.即在在起始位置,中间位置,末尾位置中选出中间值,作为基准值
2.递归到小的子区间时,可以考虑使用插入排序

三数取中

template<class T, class C>
int Sort<T, C>::midinTree(vector<T>& v, int left, int right) {
	int mid = (left + right) / 2;
	int mid_val = min(max(v[left], v[mid]), max(v[mid], v[right]));
	if (mid_val == v[mid]) 
		mySwap(v[mid], v[left]);
	else if (mid_val == v[right]) 
		mySwap(v[left], v[right]);
	return v[left];
}

我们可以在定义基准值的时候使用三数取中函数,样例的三数取中函数标准值默认最左

int mark = midinTree(v, begin, end);							

递归小区间
因为我们上面写的插入排序是针对一整个vector的,若想要在递归小区间使用则需要构建一个带left,和right的重载函数,思路很简单这里就不演示了

7. 归并排序

基本思想:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

7.1 归并排序(递归实现)

在这里插入图片描述

递归实现

template<class T, class C>
void Sort<T, C>::MergeSort(vector<T>& v) {
	vector<T> tmp(v.size());
	_MergeSort(v, tmp, 0, v.size() - 1);               // 调用子函数
}

template<class T, class C>
void Sort<T, C>::_MergeSort(vector<T>& v, vector<T>& tmp, int left, int right) {
	if (left >= right) return;                         // 若最多只有一个元素则退出
	int mid = (left + right) / 2;                      
	_MergeSort(v, tmp, left, mid);                     // 将左半部分进行归并排序
	_MergeSort(v, tmp, mid + 1, right);                // 将右半部分进行归并排序
	// 走到这左半部分和右半部分都已经有序,并且左右至少都有一个数字
	int left_cur = left, right_cur = mid + 1;          // 记录左,右半部分现元素下标,
	for (int i = left; i <= right; i++) {              // 需要将tmp [left, right] 数据进行填充
		if (left_cur <= mid && right_cur <= right) {   // 若左半部分和右半部分都还有剩余元素
			if (comp(v[left_cur], v[right_cur])) {     // 若左比较大, 则选左元素,并且左部分下标++
				tmp[i] = v[left_cur++];
			}
			else {                                     // 右比较大,选右元素,并且右部分下标++
				tmp[i] = v[right_cur++];
			}
		}
		else if (left_cur > mid) tmp[i] = v[right_cur++];  // 左部分走完了,一直将右部分走完
		else if (right_cur > right) tmp[i] = v[left_cur++];// 右部分走完了,一直将左部分走完
		else break;
	}
	copy(tmp.begin() + left, tmp.begin() + right + 1, v.begin() + left); //将tmp中排序好的[left, right]里的数据拷贝到v中

测试代码

void MergeSortTest() {
	vector<int> v1;
	vector<int> v2{ 3, 2, 5, 1 ,4 };
	vector<int> v3;
	for (int i = 0; i < 10; i++) {
		v3.push_back(rand() % 100);
	}

	Sort<int, my_greater<int>>::MergeSort(v1);
	Sort<int, my_greater<int>>::MergeSort(v2);
	Sort<int, my_greater<int>>::MergeSort(v3);
	printVector(v1);
	printVector(v2);
	printVector(v3);
	cout << endl;
}

7.2 归并排序非递归实现

非递归实现和递归实现实际上是非常类似的,不过非递归使用的并非一分为二,而是一个元素一组,两个元素一组,四个元素一组进行分组,直到所有元素被分完
在这里插入图片描述

template<class T, class C>
void Sort<T, C>::nonreMergeSort(vector<T>& v) {
	if (v.size() == 0) return;
	int gap = 1, end = v.size() - 1;                                     // gap 表示每一组的个数, end表示v最后一个元素的下标
	vector<T> tmp(v.size());											 // 开辟一块和v相同大小的空间
	while (gap < v.size()) {	
		for (int i = 0; i < v.size(); i += 2 * gap) {					 // 两组合在一起进行依次循环,将两组合并					
			int left_begin = i, left_end = i + gap - 1;					 
			int right_begin = i + gap, right_end = i + 2 * gap - 1;
			if (left_end >= end) break;									 // 若左组没有gap 或者刚好只有 gap个元素, 则不需要进行排序
// 因为这组前面一定是偶数组,若前面偶数组再次合并,次数为偶,则这一组还会被单独分出来,若次数为奇数,则这组会和前面一组合并
			else if (right_end >= end) right_end = end;                  // 若右组没有gap个元素,则需要调整right_end
			for (int j = left_begin; j <= right_end; j++) {				 // 这里就是和递归一样的,将两组数据合并到tmp上
				if (left_begin <= left_end && right_begin <= right_end) {
					if (comp(v[left_begin], v[right_begin])) {
						tmp[j] = v[left_begin++];
					}
					else {
						tmp[j] = v[right_begin++];
					}
				}
				else if (left_begin > left_end) tmp[j] = v[right_begin++];
				else if (right_begin > right_end) tmp[j] = v[left_begin++];
				else break;
			}
		}
		v.swap(tmp);	// 将tmp处理好的数据转移到v中											
		gap *= 2;		// 组距加倍
	}
}

测试代码

void nonreMergeSortTest() {
	vector<int> v1;
	vector<int> v2{ 3, 2, 5, 1 ,4 };
	vector<int> v3;
	for (int i = 0; i < 10; i++) {
		v3.push_back(rand() % 100);
	}
	Sort<int>::nonreMergeSort(v1);
	Sort<int>::nonreMergeSort(v2);
	Sort<int>::nonreMergeSort(v3);
	printVector(v1);
	printVector(v2);
	printVector(v3);
	cout << endl;
}

归并排序特性

  • 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  • 时间复杂度:O(N*logN)
  • 空间复杂度:O(N)
  • 稳定性:稳定

8. 计数排序

基本思想:
计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。操作步骤:
1.统计相同元素出现次数
2.根据统计的结果将序列回收到原来的序列中
在这里插入图片描述
计数排序的特性总结:

  • 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
  • 时间复杂度:O(MAX(N,范围))
  • 空间复杂度:O(范围)
  • 稳定性:稳定

排序算法复杂度及稳定性分析

在这里插入图片描述
在这里插入图片描述

代码

Sort类实现代码

#include <vector>
#include <iostream>
#include <cstdlib>
#include <queue>
#include <stack>
using namespace std;
//template<class T>
//void mySwap(T& a, T& b) {
//		T tmp = a;
//		a = b;
//		b = tmp;
//	}

template<class T>
struct my_less {
	bool operator()(const T& a, const T& b) { return a > b; }
};

template<class T>
struct my_greater {
	bool operator()(const T& a, const T& b) { return a < b; }
};
template<class T, class C = my_less<T>>
class Sort{
public:
	static C comp;
	static void mySwap(T& a, T& b) { T tmp = a; a = b; b = tmp; } //交换两个元素
	static void InsertSort(vector<T>& v);		// 直接插入排序
	static void ShellSort(vector<T>& v);		// 希尔排序
	static void SelectSort(vector<T>& v);		// 选择排序
	static void BubbleSort(vector<T>& v);		// 冒泡排序
	static void HeapSort(vector<T>& v);			// 堆排序
	static void MergeSort(vector<T>& v);		// 归并排序(递归)
	static void nonreMergeSort(vector<T>& v);   // 归并排序(非递归)

	//快速排序
	static void hoareQuickSort(vector<T>& v);   // hoare版快速排序(递归)
	static void digpitQuickSort(vector<T>& v);  // 挖坑版快速排序(递归)
	static void froaftQuickSort(vector<T>& v);  // 前后指针版快速排序(递归)
	static void nonreQuickSort(vector<T>& v);   // 快速排序(非递归)
	static int midinTree(vector<T>& v, int left, int right); // 三数取中(优化)

	//递归子函数
	static void _MergeSort(vector<T>& v, vector<T>& tmp, int left, int right);
	static void _hoareQuickSort(vector<T>& v, int begin, int end);
	static void _digpitQuickSort(vector<T>& v, int begin, int end);
	static void _froaftQuickSort(vector<T>& v, int left, int right);
};

template<class T, class C>
C Sort<T, C>::comp;

template<class T, class C>
void  Sort<T, C>::InsertSort(vector<T>& v) {
	for (int i = 0; i < v.size(); i++) { // i 表示准备插入数据的下标
		if (i == 0) continue;
		int temp = v[i], cur = i - 1;    // temp表示准备插入的数据 cur为对比的数据下标
		while (cur >= 0) {               // 当cur < 0 时说明没有数据比temp大,则退出
			if (comp(temp, v[cur])) {    // 默认是降序的情况下,若temp > v[cur]则进入
				v[cur + 1] = v[cur];     // 将cur 位置的数据移动至cur + 1
				cur--;                   // 向前寻找插入位置
			}
			else {                       // 说明前面[0, cur]数据均大于temp,[cur + 1, i]位置均小于temp
				break;
			}
		}
		v[cur + 1] = temp;               // 在cur + 1位置插入temp      
	}
}

template<class T, class C> 
void Sort<T, C>::ShellSort(vector<T>& v) {
	int gap = v.size();                 // 步长
	while (gap > 1) {                   // 当 gap == 1时结束,并且数组为空时不会进入
		gap = gap / 3 + 1;              // gap不断减小
		for (int i = 0; i < v.size() - gap; i++) {
			int temp = v[i + gap];      // temp表示准备插入的数据 
			int cur = i;				// cur为对比的数据下标
			while (cur >= 0) {
				if (comp(temp, v[cur])) { // 默认是降序的情况下,若temp > v[cur]则进入
					v[cur + gap] = v[cur];
					cur -= gap;
				}
				else {
					break;				// 说明前面[0, cur]数据均大于temp,[cur + 1, i]位置均小于temp
				}
			}
			v[cur + gap] = temp;		// 在cur + 1位置插入temp    
		}
	}
}

template<class T, class C>
void Sort<T, C>::SelectSort(vector<T>& v) {
	if (v.size() == 0) return;
	int begin = 0, end = v.size() - 1;
	while (begin < end) {
		int max = begin, min = begin;
		for (int i = begin; i <= end; i++) {
			if (comp(v[i], v[max])) max = i;
			if (comp(v[min], v[i])) min = i;
		}
		mySwap(v[begin], v[max]);
		mySwap(v[end], v[min]);
		begin++;
		end--;
	}
}   

template<class T, class C>
void Sort<T, C>::BubbleSort(vector<T>& v) {
	if (v.size() == 0) return;
	for (int i = 0; i < v.size() - 1; i++) {          // 每次选一个数字,需要选v.size() - 1次
		for (int j = 0; j < v.size() - i - 1; j++) {  // 第i次有v.size() - i个数字进行冒泡,需要进行v.size() - 1次比较
			if (comp(v[j + 1], v[j])) {               // 若v[j + 1] > v[j] 则将较小数往后移动
				swap(v[j + 1], v[j]);
			}
		}
	}
}   // 冒泡排序

template<class T, class C>
void Sort<T, C>::HeapSort(vector<T>& v) {
	if (comp(1, 0)) {
		priority_queue<T, vector<int>, less<int>> heap(v.begin(), v.end()); // 降序使用大堆
		for (int i = 0; i < v.size(); i++) {
			v[i] = heap.top();                         // 堆顶元素最大
			heap.pop();								   // 删除最大元素
		}
	}
	else {
		priority_queue<T, vector<int>, greater<int>> heap(v.begin(), v.end()); // 升序使用小堆
		for (int i = 0; i < v.size(); i++) {
			v[i] = heap.top();
			heap.pop();
		}
	}
}     // 堆排序

template<class T, class C>
void Sort<T, C>::MergeSort(vector<T>& v) {
	vector<T> tmp(v.size());
	_MergeSort(v, tmp, 0, v.size() - 1);               // 调用子函数
}

template<class T, class C>
void Sort<T, C>::_MergeSort(vector<T>& v, vector<T>& tmp, int left, int right) {
	if (left >= right) return;                         // 若最多只有一个元素则退出
	int mid = (left + right) / 2;                      
	_MergeSort(v, tmp, left, mid);                     // 将左半部分进行归并排序
	_MergeSort(v, tmp, mid + 1, right);                // 将右半部分进行归并排序
	// 走到这左半部分和右半部分都已经有序,并且左右至少都有一个数字
	int left_cur = left, right_cur = mid + 1;          // 记录左,右半部分现元素下标,
	for (int i = left; i <= right; i++) {              // 需要将tmp [left, right] 数据进行填充
		if (left_cur <= mid && right_cur <= right) {   // 若左半部分和右半部分都还有剩余元素
			if (comp(v[left_cur], v[right_cur])) {     // 若左比较大, 则选左元素,并且左部分下标++
				tmp[i] = v[left_cur++];
			}
			else {                                     // 右比较大,选右元素,并且右部分下标++
				tmp[i] = v[right_cur++];
			}
		}
		else if (left_cur > mid) tmp[i] = v[right_cur++];  // 左部分走完了,一直将右部分走完
		else if (right_cur > right) tmp[i] = v[left_cur++];// 右部分走完了,一直将左部分走完
		else break;
	}
	copy(tmp.begin() + left, tmp.begin() + right + 1, v.begin() + left); //将tmp中排序好的[left, right]里的数据拷贝到v中
}

template<class T, class C>
void Sort<T, C>::nonreMergeSort(vector<T>& v) {
	if (v.size() == 0) return;
	int gap = 1, end = v.size() - 1;                                     // gap 表示每一组的个数, end表示v最后一个元素的下标
	vector<T> tmp(v.size());											 // 开辟一块和v相同大小的空间
	while (gap < v.size()) {	
		for (int i = 0; i < v.size(); i += 2 * gap) {					 // 两组合在一起进行依次循环,将两组合并					
			int left_begin = i, left_end = i + gap - 1;					 
			int right_begin = i + gap, right_end = i + 2 * gap - 1;
			if (left_end >= end) break;									 // 若左组没有gap 或者刚好只有 gap个元素, 则不需要进行排序
// 因为这组前面一定是偶数组,若前面偶数组再次合并,次数为偶,则这一组还会被单独分出来,若次数为奇数,则这组会和前面一组合并
			else if (right_end >= end) right_end = end;                  // 若右组没有gap个元素,则需要调整right_end
			for (int j = left_begin; j <= right_end; j++) {				 // 这里就是和递归一样的,将两组数据合并到tmp上
				if (left_begin <= left_end && right_begin <= right_end) {
					if (comp(v[left_begin], v[right_begin])) {
						tmp[j] = v[left_begin++];
					}
					else {
						tmp[j] = v[right_begin++];
					}
				}
				else if (left_begin > left_end) tmp[j] = v[right_begin++];
				else if (right_begin > right_end) tmp[j] = v[left_begin++];
				else break;
			}
		}
		v.swap(tmp);	// 将tmp处理好的数据转移到v中											
		gap *= 2;		// 组距加倍
	}
}

template<class T, class C>
int Sort<T, C>::midinTree(vector<T>& v, int left, int right) {
	int mid = (left + right) / 2;
	int mid_val = min(max(v[left], v[mid]), max(v[mid], v[right]));
	if (mid_val == v[mid]) 
		mySwap(v[mid], v[left]);
	else if (mid_val == v[right]) 
		mySwap(v[left], v[right]);
	return v[left];
}

template<class T, class C>
void Sort<T, C>::hoareQuickSort(vector<T>& v) {
	if (v.size() == 0) return;
	_hoareQuickSort(v, 0, v.size() - 1);
}   

// 5 3 4 1 2
template<class T, class C>
void Sort<T, C>::_hoareQuickSort(vector<T>& v, int begin, int end) {
	if (begin >= end) return;							
	int mark = midinTree(v, begin, end);					// 设最左边的元素为基准值
	int left = begin, right = end;							// 设置边界,注意这里left不能越过基准值,因为若已经有序,右边指针会不断左移至基准值
	while (left < right) {
		while (left < right && !comp(v[right], mark)) {     // 从右开始找到比基准值大的元素, !加仿函数可以表示 >=, <= 等
			right--;
		}
		while (left < right && !comp(mark, v[left])) {		// 从左开始找比基准值小的元素
			left++;
		}
		mySwap(v[left], v[right]);							
	}
	mySwap(v[begin], v[left]);
	_hoareQuickSort(v, begin, left - 1);					// 此时left左边均大于基准值,对左边进行排序
	_hoareQuickSort(v, left + 1, end);						// 此时right右边均小于基准值,对右边进行排序
}

template<class T, class C>
void Sort<T, C>::digpitQuickSort(vector<T>& v) {
	if (v.size() == 0) return;
	_digpitQuickSort(v, 0, v.size() - 1);
}

template<class T, class C>
void Sort<T, C>::_digpitQuickSort(vector<T>& v, int begin, int end) {
	if (begin >= end) return;								// 若没有元素或只有一个元素则已经有序
	int mark = v[end], pit_index = end;						// 设基准值是最右边的元素, 坑的下标为pit_index
	int left = begin; int right = end;	
	while (left < right) {
		while (left < right && !comp(mark, v[left])) {		// 从左边开始找比基准值小的数字,若大于等于基准值则跳过
			left++;
		}
		if (left < right) {									
			v[pit_index] = v[left];							// 找到比基准值小的数字,则将小的数字填充到坑位中	
			pit_index = left;								// 将现位置更新为新的坑
		}
		while (left < right && !comp(v[right], mark)) {		// 从右边开始找比基准值大的数字,若小于等于基准值则跳过
			right--;
		}
		if (left < right) {
			v[pit_index] = v[right];						// 找到比基准值小的数字,则将大的数字填充到坑位中		
			pit_index = right;								// 更新坑位
		}
	}
	v[left] = mark;											// 左右指针相遇,将此处更新为mark基准值
	_digpitQuickSort(v, begin, left - 1);
	_digpitQuickSort(v, left + 1, end);
}

template<class T, class C>
void Sort<T, C>::froaftQuickSort(vector<T>& v) {
	if (v.empty()) return;
	_froaftQuickSort(v, 0, v.size() - 1);
}

template<class T, class C>
void Sort<T, C>::_froaftQuickSort(vector<T>& v, int begin, int end) {
	if (begin >= end) return;
	int prev = begin, cur = begin + 1;        // 定义快指针慢指针
	int mark = v[begin];					  // 定义基准值
	while (cur <= end) {					  // 快指针若超过范围则退出
		if (comp(v[cur], mark) && ++prev != cur) { // 如果快指针指向元素大于基准值,并且prev不指向基准值才可交换
			mySwap(v[cur], v[prev]);		  // 替换快慢指针中的元素					        	
		}
		cur++;								  // 快指针一直往后走	
	}	
	mySwap(v[begin], v[prev]);				  // 将基准值和v[prev]交换 v[prev]一定小于等于基准值	
	_froaftQuickSort(v, begin, prev - 1);     // [begin, prev - 1]元素一定都大于等于基准值
	_froaftQuickSort(v, prev + 1, end);		      // [prev, end] 元素一定小于基准值
}

template<class T, class C>
void Sort<T, C>::nonreQuickSort(vector<T>& v) {
	if (v.empty()) return;
	stack<int> st;												// 使用栈来记录单趟排序的左右边界
	st.push(v.size() - 1);										// 试着初始边界
	st.push(0);
	while (!st.empty()) {
		int begin = st.top(); st.pop();							// 拿出边界,注意边界插入和拿去顺序是相反的
		int end = st.top();   st.pop();							
		int mark = v[end], pit_index = end;						// 设基准值是最右边的元素, 坑的下标为pit_index
		int left = begin; int right = end;
		while (left < right) {
			while (left < right && !comp(mark, v[left])) {		// 从左边开始找比基准值小的数字,若大于等于基准值则跳过
				left++;
			}
			if (left < right) {
				v[pit_index] = v[left];							// 找到比基准值小的数字,则将小的数字填充到坑位中	
				pit_index = left;								// 将现位置更新为新的坑
			}
			while (left < right && !comp(v[right], mark)) {		// 从右边开始找比基准值大的数字,若小于等于基准值则跳过
				right--;
			}
			if (left < right) {
				v[pit_index] = v[right];						// 找到比基准值小的数字,则将大的数字填充到坑位中		
				pit_index = right;								// 更新坑位
			}
		}
		v[left] = mark;											// 此时[begin, left - 1] 均大于等于基准值[left + 1, end]均小于等于基准值
		if (begin < left - 1) {									// 将左右部分的左右边界放入栈,若左右部分只有一个元素则直接跳过
			st.push(left - 1);					
			st.push(begin);
		}
		if (left + 1 < end) {
			st.push(end);
			st.push(left + 1);
		}
	}
}

测试代码

#include "Sort.h"

void printVector(vector<int>& v) {
	for (const auto& el : v) {
		cout << el << " ";
	}
	cout << endl;
}

void insertSortTest() {
	vector<int> v1;
	vector<int> v2{ 3, 2, 5, 1 ,4 };
	vector<int> v3;
	for (int i = 0; i < 10; i++) {
		v3.push_back(rand() % 100);
	}

	Sort<int>::InsertSort(v1);
	Sort<int>::InsertSort(v2);
	Sort<int>::InsertSort(v3);
	printVector(v1);
	printVector(v2);
	printVector(v3);
	cout << endl;
}
void shellSortTest() {
	vector<int> v1;
	vector<int> v2{ 3, 2, 5, 1 ,4 };
	vector<int> v3;
	for (int i = 0; i < 10; i++) {
		v3.push_back(rand() % 100);
	}

	Sort<int>::ShellSort(v1);
	Sort<int>::ShellSort(v2);
	Sort<int>::ShellSort(v3);
	printVector(v1);
	printVector(v2);
	printVector(v3);
	cout << endl;
}

void selectSortTest() {
	vector<int> v1;
	vector<int> v2{ 3, 2, 5, 1 ,4 };
	vector<int> v3;
	for (int i = 0; i < 10; i++) {
		v3.push_back(rand() % 100);
	}

	Sort<int>::SelectSort(v1);
	Sort<int>::SelectSort(v2);
	Sort<int>::SelectSort(v3);
	printVector(v1);
	printVector(v2);
	printVector(v3);
	cout << endl;
}

void bubbleSortTest() {
	vector<int> v1;
	vector<int> v2{ 3, 2, 5, 1 ,4 };
	vector<int> v3;
	for (int i = 0; i < 10; i++) {
		v3.push_back(rand() % 100);
	}

	Sort<int>::BubbleSort(v1);
	Sort<int>::BubbleSort(v2);
	Sort<int>::BubbleSort(v3);
	printVector(v1);
	printVector(v2);
	printVector(v3);
	cout << endl;
}

void heapSortTest() {
	vector<int> v1;
	vector<int> v2{ 3, 2, 5, 1 ,4 };
	vector<int> v3;
	for (int i = 0; i < 10; i++) {
		v3.push_back(rand() % 100);
	}

	Sort<int, my_greater<int>>::HeapSort(v1);
	Sort<int, my_greater<int>>::HeapSort(v2);
	Sort<int, my_greater<int>>::HeapSort(v3);
	printVector(v1);
	printVector(v2);
	printVector(v3);
	cout << endl;
}

void MergeSortTest() {
	vector<int> v1;
	vector<int> v2{ 3, 2, 5, 1 ,4 };
	vector<int> v3;
	for (int i = 0; i < 10; i++) {
		v3.push_back(rand() % 100);
	}

	Sort<int, my_greater<int>>::MergeSort(v1);
	Sort<int, my_greater<int>>::MergeSort(v2);
	Sort<int, my_greater<int>>::MergeSort(v3);
	printVector(v1);
	printVector(v2);
	printVector(v3);
	cout << endl;
}

void nonreMergeSortTest() {
	vector<int> v1;
	vector<int> v2{ 3, 2, 5, 1 ,4 };
	vector<int> v3;
	for (int i = 0; i < 10; i++) {
		v3.push_back(rand() % 100);
	}
	Sort<int>::nonreMergeSort(v1);
	Sort<int>::nonreMergeSort(v2);
	Sort<int>::nonreMergeSort(v3);
	printVector(v1);
	printVector(v2);
	printVector(v3);
	cout << endl;
}

void hoareQuickSortTest() {
	vector<int> v1;
	vector<int> v2{ 3, 2, 5, 1 ,4 };
	vector<int> v3;
	for (int i = 0; i < 10; i++) {
		v3.push_back(rand() % 100);
	}
	Sort<int>::hoareQuickSort(v1);
	Sort<int>::hoareQuickSort(v2);
	Sort<int>::hoareQuickSort(v3);
	printVector(v1);
	printVector(v2);
	printVector(v3);
	cout << endl;
}

void digpitQuickSortTest() {
	vector<int> v1;
	vector<int> v2{ 3, 2, 5, 1 ,4 };
	vector<int> v3;
	for (int i = 0; i < 10; i++) {
		v3.push_back(rand() % 100);
	}
	Sort<int>::digpitQuickSort(v1);
	Sort<int>::digpitQuickSort(v2);
	Sort<int>::digpitQuickSort(v3);
	printVector(v1);
	printVector(v2);
	printVector(v3);
	cout << endl;
}

void froaftQuickSortTest() {
	vector<int> v1;
	vector<int> v2{ 3, 3, 2, 2, 4, 5, 1 ,4 };
	vector<int> v3;
	for (int i = 0; i < 10; i++) {
		v3.push_back(rand() % 100);
	}
	Sort<int>::froaftQuickSort(v1);
	Sort<int>::froaftQuickSort(v2);
	Sort<int>::froaftQuickSort(v3);
	printVector(v1);
	printVector(v2);
	printVector(v3);
	cout << endl;
}

void nonreQuickSortTest() {
	vector<int> v1;
	vector<int> v2{ 3, 3, 2, 2, 4, 5, 1 ,4 };
	vector<int> v3;
	for (int i = 0; i < 10; i++) {
		v3.push_back(rand() % 100);
	}
	Sort<int>::nonreQuickSort(v1);
	Sort<int>::nonreQuickSort(v2);
	Sort<int>::nonreQuickSort(v3);
	printVector(v1);
	printVector(v2);
	printVector(v3);
	cout << endl;
}

int main() {
	//insertSortTest();
	//shellSortTest();
	//selectSortTest();
	//bubbleSortTest();
	//heapSortTest();
	//MergeSortTest();
	//nonreMergeSortTest();
	//hoareQuickSortTest();
	//digpitQuickSortTest();
	//froaftQuickSortTest();
	//nonreQuickSortTest();
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/157862.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker搭建PHP运行环境

目录 Docker 安装 PHP Docker 安装 Nginx ​编辑运行nginx容器 nginx安装成功 Nginx PHP 部署PHP项目 启动 PHP&#xff1a; 启动 nginx&#xff1a; 查看正在运行的容器: 访问域名测试搭建结果 Docker相关命令描述 Docker 安装 PHP 这里我们拉取官方的镜像,标签…

代码随想录算法训练营第8天 344.反转字符串、541. 反转字符串II、剑指Offer58-II.左旋转字符串

代码随想录算法训练营第8天 344.反转字符串、541. 反转字符串II、剑指Offer58-II.左旋转字符串 反转字符串 力扣题目链接(opens new window) 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 对于字符串&#xff0c;我…

Linux驱动开发基础__Linux 系统对中断处理的演进

目录 1 Linux 对中断的扩展&#xff1a;硬件中断、软件中断 2 中断处理原则 1&#xff1a;不能嵌套 3 中断处理原则 2&#xff1a;越快越好 4 要处理的事情实在太多&#xff0c;拆分为&#xff1a;上半部、下半部 5 下半部要做的事情耗时不是太长&#xff1a;tasklet…

154. 滑动窗口

文章目录QuestionIdeasCodeQuestion 给定一个大小为 n≤106 的数组。 有一个大小为 k 的滑动窗口&#xff0c;它从数组的最左边移动到最右边。 你只能在窗口中看到 k 个数字。 每次滑动窗口向右移动一个位置。 以下是一个例子&#xff1a; 该数组为 [1 3 -1 -3 5 3 6 7]&…

知识点滴 - 数据库视图概念

视图是数据库中一个非常简单的概念&#xff0c;写过SQL的人几乎大致了解视图。本文除了在回顾视图的本质及相关操作知识时&#xff0c;会重点阐述它蕴含的分层思想在数据分析工作中的作用。 1&#xff0c;视图的本质与作用 视图是一个数据库中的虚拟表&#xff0c;它的本质是S…

模板特化与static成员初始化

我们知道在 c 的类中&#xff0c;如果有static成员数据&#xff0c;则需要在类外进行定义&#xff0c;而类内那只是声明。这个在类模板中也是一样的&#xff0c;需要在类外进行定义。普通类模板的 static 数据的初始化&#xff0c;如下代码&#xff1a; template <class T&…

SpringBoot在Controller层接收参数的常用方法(超详细)

前言 在工作中&#xff0c;比如要实现一个功能&#xff0c;前端传什么参数&#xff0c;后端的controller层中怎么接收参数 &#xff0c;封装成了什么实体对象&#xff0c;有些参数是在URL上使用&#xff0c;有些参数是在body上使用&#xff0c;service层中做了什么逻辑&#xf…

数据结构(根据王道整理)

数据结构 文章目录数据结构线性结构与非线性结构链表kmp算法栈二叉树完全二叉树二叉树的存储结构二叉树的访问树的深度二叉树的层次遍历由遍历序列构造二叉树已知后序跟中序建立二叉树线索二叉树序言&#xff08;土办法解决找前驱&#xff09;线索二叉树存储结构中序线索二叉树…

几道基础的二叉树、树的题

几道基础的二叉树、树的题LeetCode144.二叉树的前序遍历思路及实现方法一&#xff1a;递归方法二&#xff1a;迭代LeetCode145.二叉树的后序遍历思路及实现方法一&#xff1a;递归方法二&#xff1a;迭代LeetCode94.二叉树的中序遍历思路及实现方法一&#xff1a;递归方法二&am…

数据结构(2)树状数组

活动 - AcWing 参考&#xff1a;《算法竞赛进阶指南》-lyd 目录 一、概念 1.主要功能 2.实现方式 3. 二、例题 1.树状数组和逆序对 2.树状数组和差分 3. 两层差分 4. 结合二分 一、概念 1.主要功能 树状数组可以完成的功能主要有&#xff1a; 维护序列的前缀和单…

pytest-pytest插件之测试覆盖率pytest-cov

简介 测试覆盖率是指项目代码被测试用例覆盖的百分比&#xff0c;使用pytest-cov插件可以统计测试覆盖率 添加链接描述 安装插件pytest-cov pip install pytest-cov用法 基本用法 –cov的参数是要统计代码覆盖率的源码&#xff0c;我将源码放在mysrc中&#xff0c;test_s…

qiankun微应用加载第三方js跨域报错

当我们在qiankun微应用&#xff0c;引入第三方js脚本时会产生跨域问题并报错&#xff0c;看qiankun的解释&#xff1a;常见问题 - qiankunqiankun会把静态资源的加载拦截&#xff0c;改用fetch方式获取资源&#xff0c;所以要求这些资源支持跨域。虽然qiankun也提供了解决方案&…

react面试题--react入门小案例案例

React入门应该是这样的 源码&#xff1a;https://github.com/dansoncut/React-beginner-tutorial-TeacherEgg.git 视频地址&#xff1a;https://www.bilibili.com/video/BV1be411w7iF/?spm_id_from333.337.search-card.all.click&vd_sourceae42119b44d398cd8fe181740c3e…

Java线程的六种状态

前言&#xff1a;其实线程的状态在操作系统的PCB中就对其进行了描述&#xff0c;但是Java中觉得自带的状态并不是特别好&#xff0c;于是引入了线程在Java中的六种状态。 (1) NEW 安排了工作还未行动&#xff0c;即&#xff1a;Thread对象创建出来了&#xff0c;但是内核的PCB…

开源工具 tomcat

Tomcat 封装了很多HTTP的操作&#xff1a;负责解析 HTTP 协议&#xff0c;解析请求数据&#xff0c;并发送响应数据。 官网 download下的which version&#xff1a; Apache Tomcat - Which Version Do I Want? 可以看tomcat对jdk的版本要求。 启动 启动&#xff1a;双击…

【redis6】第六章(新数据类型)

Bitmaps 简介 现代计算机用二进制&#xff08;位&#xff09;作为信息的基础单位&#xff0c; 1个字节等于8位&#xff0c; 例如“abc”字符串是由3个字节组成&#xff0c; 但实际在计算机存储时将其用二进制表示&#xff0c; “abc”分别对应的ASCII码分别是97、 98、 99&am…

SEO优化收徒站外引蜘蛛软件方法

SEO优化收徒站外引蜘蛛软件方法 今天我们讲解站外引蜘蛛的方法&#xff0c;站外引蜘蛛的方法无非就是五个大点。 第一个是搜索引擎的提交&#xff0c;我们通过是百度资源站展或者 360 或者神马头条&#xff0c;搜狗 bin 等等这样的一个搜索引擎去提交我们的链接。 里面主要是…

【css】结构选择器

结构选择器&#xff0c;也称之为组合器选择器&#xff0c;根据它们之间的特定关系来选取元素。CSS 中有四种不同的组合器&#xff1a;后代选择器 (空格)子选择器 (>)相邻兄弟选择器 ()通用兄弟选择器 (~)选择器示例描述element elementdiv p选择 div 元素内部的所有 p 元素e…

仗剑走天涯是梦想,仗键走天涯是坚持

在这信息化、数字化浪潮发展中&#xff0c;人们办公、娱乐、学习、生活都离不开了手机电脑平板等一系列电子设备&#xff0c;互联网行业工作者更是不可避免的需要频繁接触到电脑、键盘、鼠标等设备&#xff0c;今天给大家推荐一款性价比极高的键盘Keychron K3 Pro 一、keychron…

小程序API Promise化

一、 应用场景 小程序页面初始化时&#xff0c;需要去服务端获取token&#xff0c;所带参数在另外两个接口请求中&#xff0c;所写代码可能是这样子的&#xff1a; onLoad(options) {this.getToken() }, getToken() {wx.request({url: 后端API地址1,success: (res) > {//…