【八大排序】冒泡排序 | 快速排序 + 图文详解!!

news2024/9/24 15:21:46

在这里插入图片描述

📷 江池俊: 个人主页
🔥个人专栏: ✅数据结构冒险记 ✅C语言进阶之路
🌅 有航道的人,再渺小也不会迷途。


在这里插入图片描述

文章目录

    • 交换排序
    • 一、冒泡排序
      • 1.1 算法步骤 + 动图演示
      • 1.2 冒泡排序的效率分析
      • 1.3 代码实现
      • 1.4 冒泡排序特性总结
    • 二、快速排序
      • ✨为什么要三数取中?
      • ✨为什么要进行小区间优化?
      • 2.1 hoare版本 + 动图演示
      • 2.2 挖坑法 + 动图演示
      • 2.3 前后指针法 + 动图演示
      • 2.4 快排的`非递归`
      • 2.5 快速排序特性总结

在这里插入图片描述

交换排序

基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置。
交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。


一、冒泡排序

冒泡排序(Bubble Sort)是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢 “浮” 到数列的顶端。

冒泡排序有一种优化算法,就是立一个 flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序。但这种改进对于提升性能来说并没有什么太大作用。

1.1 算法步骤 + 动图演示

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

在这里插入图片描述

1.2 冒泡排序的效率分析

  1. 什么时候最快?
    当输入的数据已经是正序时(都已经是正序了,我还要你冒泡排序有何用啊)。

  2. 什么时候最慢?
    当输入的数据是反序时(写一个 for 循环反序输出数据不就行了,干嘛要用你冒泡排序呢,我是闲的吗)。

1.3 代码实现

void Swap(int* p1, int* p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}

// 冒泡排序
// 时间复杂度:O(N^2)
// 最好情况是多少:O(N)
void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		bool exchange = false;
		for (int j = 1; j < n - i; j++)
		{
			if (a[j - 1] > a[j])
			{
				Swap(&a[j - 1], &a[j]);
				exchange = true;
			}
		}
		if (exchange == false)
			break;
	}
}

1.4 冒泡排序特性总结

  1. 冒泡排序是一种非常容易理解的排序
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定

二、快速排序

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

建议:此处优化内容可以在学完一两个快排思路后再进行观看。
快速排序优化:

  1. 三数取中法选key(即尽可能将不大不小的数作为基准数)
  2. 递归到小的子区间时,可以考虑使用插入排序(小区间优化)

✨为什么要三数取中?

  1. 减少最坏情况发生的概率:在快速排序中,如果每次选择的基准值都是当前序列中的最大或最小元素,那么将导致最坏情况的发生,即每次划分只能将原序列分为两个子序列,一个为空,另一个几乎包含所有剩余元素。这种情况下,快速排序的时间复杂度会退化到O(n^2)。通过三数取中,可以减少选择到极端值作为基准值的可能性,从而提高算法的平均性能。
  2. 提高算法的稳定性:三数取中的方法通常涉及选取序列的左端、中间和右端三个元素,然后从中选择一个值作为基准值。这样的方法能够更好地反映整个序列的中位数,从而使得划分更加均衡。
  3. 适应不同的数据分布:不同的数据分布对基准值的选择有不同的影响。三数取中的方法能够在一定程度上适应不同的数据分布,提高算法的普适性和鲁棒性。

总的来说,三数取中是快速排序中的一种优化策略,旨在通过选择一个更好的基准值来提高排序效率,减少最坏情况的发生概率,并使算法更加稳定和鲁棒。

【三数取中代码实现】

算法步骤

  1. 首先,计算数组的中间位置midi
  2. 判断中间值与首尾值的关系,根据不同的关系来决定返回哪个值作为基准值。
  3. 如果首值大于中间值,则:
    • 如果中间值大于尾值,返回中间值。
    • 否则,如果首值大于尾值,返回尾值。
    • 否则,返回首值。
  4. 如果首值小于等于中间值(即a[begin] <= a[midi]),则:
    • 如果中间值小于尾值,返回中间值。
    • 否则,如果首值小于尾值,返回尾值。
    • 否则,返回首值。
// 三数取中
int GetMidi(int* a, int begin, int end)
{
	int midi = (begin + end) / 2;
	// begin midi end 三个数选中位数
	if (a[begin] > a[midi])
	{
		if (a[midi] > a[end])
			return midi;
		else if (a[begin] > a[end]) 
			return end;
		else 
 			return begin;
	}
	else  // a[begin] <= a[midi]
	{
		if (a[midi] < a[end])
			return midi;
		else if (a[begin] < a[end]) 
			return end;
		else  // a[end] <= a[begin] <= a[midi] 
			return begin;
	}
}

✨为什么要进行小区间优化?

  1. 减少递归深度:当快速排序处理的数组或子数组较小时,递归的深度会减少,从而能够更快地完成排序,减少函数调用开销。
  2. 避免栈溢出:对于较小的子数组使用非递归的方法可以避免在深层递归时出现栈溢出的问题。
  3. 提升平均性能:小区间优化通过结合递归和迭代,能够有效平衡不同情况下的性能,从而提升快速排序的平均性能。

综上所述,小区间优化是快速排序中一个重要的性能提升措施,它有助于降低算法在实际应用中的最坏情况时间复杂度,提高整体的排序效率。

【小区间优化快排代码】

// 快排(霍尔法 + 小区间优化)
void QuickSort1(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	// 小区间优化
	if (end - begin + 1 <= 10) // 数组元素个数为小于10时调用直接插入排序
	{
		InsertSort(a + begin, end - begin + 1);
	}
	else
	{
		// 三数取中,优化快排
		// begin midi end 三个数选中位数
		int midi = GetMidi(a, begin, end);
		Swap(&a[begin], &a[midi]);

		int left = begin, right = end;
		int keyi = begin;
		while (left < right)
		{
			// 右边找小
			while (left < right && a[right] >= a[keyi])
			{
				--right;
			}

			// 左边找大
			while (left < right && a[left] <= a[keyi])
			{
				++left;
			}

			Swap(&a[left], &a[right]);
		}

		Swap(&a[left], &a[keyi]);
		keyi = left;

		// [begin, keyi-1] keyi [keyi+1, end]
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
	}
}

2.1 hoare版本 + 动图演示

算法步骤:

  1. 选择基准值:从待排序数列中选取第一个数作为基准值。
  2. 分区:重新排列数列,所有比基准值小的元素移到基准值的左边,所有比基准值大的元素移到基准值的右边。这个过程称为一趟快速排序。
  3. 递归排序:对基准值左、右两边的子序列重复上述步骤,直到所有子序列长度不超过1,此时待排序数列就变成了有序数列。

在这里插入图片描述

void Swap(int* p1, int* p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}
// 快速排序递归实现
// hoare版快排的单趟排序
int PartSort1(int* a, int begin, int end)
{
	// 三数取中,优化快排
	// begin midi end 三个数中选中位数
	int midi = GetMidi(a, begin, end);
	Swap(&a[begin], &a[midi]);

	int left = begin, right = end;
	int keyi = begin;
	while (left < right)
	{
		// 注意:第一步是先从右边开始找小
		// 右边找小
		while (left < right && a[right] >= a[keyi])
		{
			--right;
		}

		// 左边找大
		while (left < right && a[left] <= a[keyi])
		{
			++left;
		}

		Swap(&a[left], &a[right]);
	}

	Swap(&a[left], &a[keyi]);

	return left;
}
// 快排
void QuickSort(int* a, int begin, int end)
{
    // 递归出口
	if (begin >= end)
		return;

	int keyi = PartSort1(a, begin, end);

	// [begin, keyi-1] keyi [keyi+1, end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

2.2 挖坑法 + 动图演示

算法步骤:

  1. 选择基准值:从待排序数列中选取第一个数作为基准值。
  2. 挖坑:将基准值放在待排序数列中的一个“坑”的位置上,这个“坑”的位置相当于基准值的索引。
  3. 分区:将比基准值小的元素移到基准值的左边,将比基准值大的元素移到基准值的右边。这一步实际上是在填平“坑”的过程,同时也使得基准值左边的元素都比它小,右边的元素都比它大。
  4. 递归排序:对基准值左、右两边的子序列重复上述步骤,直到所有子序列长度不超过 1,此时待排序数列就变成了有序数列。

在这里插入图片描述

void Swap(int* p1, int* p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}
// 挖坑法
int PartSort2(int* a, int begin, int end)
{
	// 三数取中,优化快排
	// begin midi end 三个数选中位数
	int midi = GetMidi(a, begin, end);
	Swap(&a[begin], &a[midi]);

	int key = a[begin]; // 保存基准值
	int hole = begin; // 初始化坑位为基准值的下标
	while (begin < end)
	{
		// 右遍找小,填到左边的坑
		while (begin < end && a[end] >= key)
		{
			--end;
		}

		a[hole] = a[end]; // 填坑
		hole = end; // 更新坑位

		// 左边找大,填到右边的坑
		while (begin < end && a[begin] <= key)
		{
			++begin;
		}

		a[hole] = a[begin]; // 填坑
		hole = begin; // 更新坑位
	}
	a[hole] = key; // 将基准值归位
	return hole; // 返回基准值的正确下标
}
// 快排
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	int keyi = PartSort2(a, begin, end);

	// [begin, keyi-1] keyi [keyi+1, end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

2.3 前后指针法 + 动图演示

算法步骤:

  1. 选择基准值:从待排序数列中选取第一个数作为基准值(key)
  2. 创建前后指针:创建两个指针,prevcurprev指向数组序列首元素位置cur指向prev的下一个位置。
  3. 遍历:通过遍历,如果cur指针找到比key小的数,然后判断prev的下一个位置是否为cur所处的位置,如果不是,则将两个值进行交换,如果是,则继续往后遍历,直到cur遍历结束。
  4. 归位:最后要将key基准值与prev指向的值进行互换,最终确认基准值处于数组序列的中间位置。
  5. 递归排序:递归地对基准值左边和右边的子数组进行排序。

在这里插入图片描述

void Swap(int* p1, int* p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}
// 前后指针法
int PartSort3(int* a, int begin, int end)
{
	// 三数取中,优化快排
	// begin midi end 三个数选中位数
	int midi = GetMidi(a, begin, end);
	Swap(&a[begin], &a[midi]);
	int keyi = begin;

	int prev = begin;
	int cur = prev + 1;
	while (cur <= end)
	{
		if (a[cur] < a[keyi] && ++prev != cur)
			Swap(&a[prev], &a[cur]);

		++cur;
	}

	Swap(&a[keyi], &a[prev]);
	return prev;
}
// 快排
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	int keyi = PartSort3(a, begin, end);

	// [begin, keyi-1] keyi [keyi+1, end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

2.4 快排的非递归

快速排序的非递归实现可以使用来模拟递归过程。以下是算法步骤和思想:

算法步骤

  1. 初始化栈:创建一个空栈,用于 存储待处理的子数组的起始和结束位置
  2. 初始状态入栈:将整个待排序数组的起始和结束位置作为初始状态入栈。
  3. 循环处理栈中的状态:当 栈不为空时,执行以下操作:
    • 弹出栈顶元素,即当前待处理的子数组的起始和结束位置
    • 进行分区操作,找到基准值的正确位置,如果 左右两个子数组起始位置小于结束位置则将左右两个子数组的起始和结束位置入栈
  4. 分区操作:在每个子数组上执行分区操作,将小于基准值的元素放在左边,大于基准值的元素放在右边。
  5. 更新栈:根据分区结果,将左子数组的起始和结束位置以及右子数组的起始和结束位置入栈
  6. 重复循环:重复步骤3-5,直到栈为空,此时整个数组已经有序

算法思想

  1. 模拟递归:通过使用栈来模拟递归调用的过程,避免了函数调用的开销。
  2. 迭代处理:通过循环处理栈中的状态,实现了对整个数组的遍历和排序。
  3. 分区操作:在每个子数组上执行分区操作,将小于基准值的元素放在左边,大于基准值的元素放在右边。
  4. 更新栈:根据分区结果,将左子数组的起始和结束位置以及右子数组的起始和结束位置入栈,以便后续处理。

总的来说,快速排序的非递归实现利用了栈来模拟递归过程,通过迭代的方式对整个数组进行遍历和排序。这种方法可以避免函数调用的开销,并且可以更直观地理解快速排序的工作原理。

辅助栈代码:

typedef int STDataType;

typedef struct Stack
{
	STDataType* a;
	int top;		// 标识栈顶位置的
	int capacity;
}ST;

// 初始化栈
void STInit(ST* pst)
{
	assert(pst);

	pst->a = NULL;
	pst->capacity = 0;

	// 表示top指向栈顶元素的下一个位置
	pst->top = 0;

	// 表示top指向栈顶元素
	//pst->top = -1;
}
// 栈的销毁
void STDestroy(ST* pst)
{
	assert(pst);

	free(pst->a);
	pst->a = NULL;
	pst->top = pst->capacity = 0;
}

// 栈顶插入删除
void STPush(ST* pst, STDataType x)
{
	assert(pst);

	if (pst->top == pst->capacity)
	{
		int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(pst->a, sizeof(STDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}

		pst->a = tmp;
		pst->capacity = newcapacity;
	}

	pst->a[pst->top] = x;
	pst->top++;
}
// 栈顶元素出栈
void STPop(ST* pst)
{
	assert(pst);
	// 不为空
	assert(pst->top > 0);

	pst->top--;
}
// 取栈顶元素
STDataType STTop(ST* pst)
{
	assert(pst);
	// 不为空
	assert(pst->top > 0);

	return pst->a[pst->top - 1];
}
// 判断栈空
bool STEmpty(ST* pst)
{
	assert(pst);

	/*if (pst->top == 0)
	{
		return true;
	}
	else
	{
		return false;
	}*/

	return pst->top == 0;
}
// 获取栈的大小
int STSize(ST* pst)
{
	assert(pst);

	return pst->top;
}

快排的非递归代码:

// 快排(非递归)
void QuickSortNonR(int* a, int begin, int end)
{
	ST s;
	STInit(&s);
	// 把区间 [begin, end]压栈
	STPush(&s, end);
	STPush(&s, begin);

	while (!STEmpty(&s))
	{
		int left = STTop(&s);
		STPop(&s);
		int right = STTop(&s);
		STPop(&s);

		int keyi = PartSort3(a, left, right); // 三种方法都可
		// [left, keyi-1] keyi [keyi+1, right]
		// 判断当前基准值左右区间是否满足条件(即区间内的元素个数是否大于1),满足就将区间位置压入栈中
		if (keyi + 1 < right)
		{
			STPush(&s, right);
			STPush(&s, keyi + 1);
		}

		if (left < keyi - 1)
		{
			STPush(&s, keyi - 1);
			STPush(&s, left);
		}
	}

	STDestroy(&s);
}

2.5 快速排序特性总结

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
  2. 时间复杂度:O(N*logN)
    在这里插入图片描述
  3. 空间复杂度:O(logN)(递归开销)
  4. 稳定性:不稳定

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

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

相关文章

HSRP配置指南

实验大纲 第 1 部分&#xff1a;验证连通性 步骤 1&#xff1a;追踪从 PC-A 到 Web 服务器的路径 步骤 2&#xff1a;追踪从 PC-B 到 Web 服务器的路径 步骤 3&#xff1a;观察当 R3 不可用时&#xff0c;网络的行为 第 2 部分&#xff1a;配置 HSRP 主用和 备用路由器 步…

【Crypto | CTF】BUUCTF rsarsa1

天命&#xff1a;第二题RSA解密啦&#xff0c;这题比较正宗 先来看看RSA算法 这道题给出了 p&#xff0c;q&#xff0c;E&#xff0c;就是给了两个质数和公钥 有这三个东西&#xff0c;那就可以得出私钥了 最后把私钥和质数放进去解密即可得到解密后的明文 from gmpy2 impor…

公交最短距离-算法

题目 给定一个一维数组&#xff0c;其中每一个元素表示相邻公交站之间的距离&#xff0c;比如有四个公交站A,B,C,D&#xff0c;对应的距离数组为&#xff0c;1,2,3,4&#xff0c;如下图示 给定目标站X和Y&#xff0c;求他们之间最短的距离 解题 遍历一次整个数组&#xff0c;…

考研/计算机二级数据结构刷题之顺序表

目录 第一题 顺序表的初始化&#xff0c;销毁&#xff0c;头插&#xff0c;尾插&#xff0c;头删&#xff0c;尾删&#xff0c;指定位置插入&#xff0c;指定删除以及打印 第二题 移除元素 题目链接&#xff1a; OJ链接 题目详解&#xff1a;移除元素 第三题&#xff1a;删…

回溯法:回溯法通用模版汇总以及模版应用

从一个问题开始 给定两个整数 n 和 k&#xff0c;返回 1 ... n 中所有可能的 k 个数的组合。 示例: 输入: n 4, k 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4] ] 很容易想到 用两个for循环就可以解决。 如果n为100&#xff0c;k为50呢&#xff0c;那就50层for循…

问题:测风站应设置在平直的巷道中,其前后()范围内不得有障碍物和拐弯等局部阻力。 #微信#媒体

问题&#xff1a;测风站应设置在平直的巷道中&#xff0c;其前后&#xff08;&#xff09;范围内不得有障碍物和拐弯等局部阻力。 参考答案如图所示

U盘里的东西刚存进去就没了怎么回事?怎么办

U盘里的东西刚存进去就没了怎么办&#xff1f;U盘是我们平时常用的存储设备之一&#xff0c;但有时候会出现一些问题。其中之一就是将东西存进去后&#xff0c;发现数据竟然消失了。这是一个令人困扰的问题&#xff0c;可能会导致我们的重要文件丢失。在本文中&#xff0c;我们…

RT-Thread线程管理(使用篇)

layout: post title: “RT-Thread线程管理” date: 2024-1-26 15:39:08 0800 tags: RT-Thread 线程管理(使用篇) 之后会做源码分析 线程是任务的载体&#xff0c;是RTT中最基本的调度单位。 线程执行时的运行环境称为上下文&#xff0c;具体来说就是各个变量和数据&#xff0c…

利用OpenCV实现物流与生产线自动化的革命性突破

背景介绍&#xff1a; 在当今高度自动化的时代&#xff0c;物流和生产线上的每一个环节都关乎企业的核心竞争力。传统的生产方式往往依赖于人工检测和操作&#xff0c;这不仅效率低下&#xff0c;而且容易出错。为了解决这一问题&#xff0c;越来越多的企业开始寻求利用计算机视…

流畅的Python(七)-函数装饰器和闭包

一、核心要义 主要解释函数装饰器的工作原理&#xff0c;包括最简单的注册装饰器和较复杂的参数化装饰器。同时&#xff0c;因为装饰器的实现依赖于闭包&#xff0c;因此会首先介绍闭包存在的原因和工作原理。 二、代码示例 1、变量作用域规则 #!/usr/bin/env python # -*-…

记录一次使用ant design 中 ConfigProvider来修改样式导致样式改变的问题(Tabs嵌套Tabs)

一 说明 继之前的一篇文章&#xff1a;antd5 Tabs 标签头的文本颜色和背景颜色修改 后&#xff0c;发现在被修改后的Tab中继续嵌套Tabs组件&#xff0c;这个新的Tabs组件样式跟外层Tabs样式也是一致的&#xff0c;如下图所示&#xff1a; 二 原因 在修改外层tabs样式时&…

学习Spring的第十三天

非自定义bean注解开发 设置非自定义bean : 用bean去修饰一个方法 , 最后去返回 , spring就把返回的这个对象,放到Spring容器 一 :名字 : 如果bean配置了参数 , 名字就是参数名 , 如果没有 , 就是方法名字 二 : 如果方法产生对象时 , 需要注入数据 , 在方法参数设置即可 , …

iOS 包含行间距计算富文本size

在一次开发过程中&#xff0c;发现带有行间距的富文本计算高度&#xff0c;会有不准确的情况&#xff0c;富文本内容明明很长&#xff0c;但是计算出的高度只有不到20像素&#xff0c;导致整个cell的高度计算异常。 需求上是文字固定宽度&#xff0c;最多显示3行&#xff0c;超…

【Simulink系列】——动态系统仿真 之 简单系统

引入 不同的系统具有不同的输入与输出。一般来说&#xff0c;输入输出数目越多&#xff0c;系统越复杂。最简单的系统只要一个输入一个输出&#xff08;SISO&#xff09;&#xff0c;且其任意时刻的输出只与当前时刻的输入有关。 一、简单系统定义 对于满足下列条件的系统&a…

android 网络拦截器统一处理请求参数和返回值加解密实现

前言 项目中遇到参数加密和返回结果加密的业务 这里写一下实现 一来加深记忆 二来为以后参考铺垫 需求 项目在开发中涉及到 登陆 发验证码 认证 等前期准备接口 这些接口需要单独处理 比如不加密 或者有其他的业务需求 剩下的是登陆成功以后的业务需求接口 针对入参和返回值…

【Android新版本兼容】onBackPressed()方法被弃用的解决方案

提示&#xff1a;此文章仅作为本人记录日常学习使用&#xff0c;若有存在错误或者不严谨得地方欢迎指正。 文章目录 一、使用 AndroidX API 实现预测性返回手势1.1 添加依赖1.2 启用返回手势1.3 注册OnBackPressedCallback()方法来处理返回手势 一、使用 AndroidX API 实现预测…

【MIT 6.S081】2020, 实验记录(5),Lab: lazy allocation

目录 Task 1: Eliminate allocation from sbrk()Task 2: Lazy allocationTask 3: Lazytests and Usertests 在学习了 page fault 这一节课后&#xff0c;了解了操作系统是如何结合 page table 和 trap 利用 page fault 来实现一系列的神奇的功能。这个 lab 就是在 XV6 中实现 l…

前端面试拼图-数据结构与算法

摘要&#xff1a;总结一些前端算法题&#xff0c;持续更新&#xff01; 一、数据结构与算法 时间复杂度-程序执行时需要的计算量&#xff08;CPU&#xff09; 空间复杂度-程序执行时需要的内存空间 前端开发&#xff1a;重时间&#xff0c;轻空间 1.把一个数组旋转k步 arr…

CSS的复合选择器

一,什么是复合选择器 常用的复合选择器有:后代选择器、子选择器、并集选择器和伪类选择器。 二,后代选择器(用空格)(重点) 后代选择器也称包含选择器,可以选择父元素里面的子元素。写法就是外层标签在前面,内层标签写后面,中间要有空格隔开。当标签发生嵌套时,内层…

PostgreSQL 也很强大,为何在中国大陆,MySQL 成为主流,PostgreSQL 屈居二线呢?

问题&#xff1a; PostgreSQL 也很强大&#xff0c;为何在中国大陆&#xff0c;MySQL 成为主流&#xff0c;PostgreSQL 屈居二线呢&#xff1f;PostgreSQL 能否替代 MySQL&#xff1f; 当我们讨论为何 MySQL 在中国大陆成为主流而 PostgreSQL 屈居二线时&#xff0c; 我们其实…