数据结构篇七:排序

文章目录

  • 前言
  • 1.插入排序
    • 1.1 基本思想
    • 1.2 代码实现
    • 1.3 特性总结
  • 2.希尔排序
    • 2.1 基本思想
    • 2.2 代码实现
    • 2.3 特性总结
  • 3. 选择排序
    • 3.1 基本思想
    • 3.2 代码实现
    • 3.3 特性总结
  • 4. 堆排序
    • 4.1 基本思想
    • 4.2 代码实现
    • 4.3 特性总结
  • 5. 冒泡排序
    • 5.1 基本思想
    • 5.2 代码实现
    • 5.3 特性总结
  • 6. 快速排序
    • 6.1 基本思想
      • 6.1.1 思想一
      • 6.1.2 思想二
      • 6.1.3 思想三
    • 6.2 代码实现
      • 6.2.1 递归版本
      • 6.2.2 非递归版本
    • 6.3 优化
    • 6.4 特性总结
  • 7.归并排序
    • 7.1 基本思想
    • 7.2 代码实现
      • 7.2.1 递归版本
      • 7.2.2 非递归版本
    • 7.3 特性总结
  • 8. 计数排序
    • 8.1 基本思想
    • 8.2 代码实现
    • 8.3 特性总结
  • 9. 总结

前言

  所谓排序,就是使一串记录按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。不同的排序算法各有优劣,本章内容讲介绍插入排序、希尔排序、选择排序、冒泡排序、堆排序、快速排序、归并排序以及计数排序八大排序算法

1.插入排序

1.1 基本思想

  插入排序是一种比较简单的排序算法,它的基本思想是:以待排序数据的第一个数据为标准,将这一个数据看为一个已排好的数据(此时只看这一个数据,先记为a),然后将它后一个数据(记为b)将 b 与 a 进行比较,如果 a 比 b 大,就将 a 向后覆盖(升序),直到遇到比 b 小的或者到数据结束停止。
  如图:
在这里插入图片描述
  图和代码一起看更容易理解一些。

1.2 代码实现

void InsertSort(int* a, int n)
{assert(a);int i = 0;for (i = 0; i < n - 1; i++){int end = i;int tmp = a[end + 1];while (end >= 0){if (a[end] > tmp){a[end + 1] = a[end];end--;}else{break;}}a[end + 1] = tmp;}
}

1.3 特性总结

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

2.希尔排序

2.1 基本思想

  先选定一个整数,把待排序数据分成个组,所有距离为 gap 的数据分在同一组内,并对每一组内的记录进行排序。然后取上述重复分组和排序的工作。当到达 gap = 1 时,所有记录在统一组内排好序。

在这里插入图片描述
  实际是先将数据分为许多组,分别进行排序,每一次排序都会使较小数移到前面,使数据逐渐接近有序。希尔排序的过程就是先进行多次预排序(分组排序的过程),一直到数据间隔 gap = 1 时完成所有排序。在实际中 gap 的初始值可给数据个数的一半,每排完一次 gap 就除 2 ,直到 gap = 1 时结束排序。

2.2 代码实现

void ShellSort(int* a, int n)
{int gap = 3;int i = 0;int j = 0;//while (gap > 1)//{//	gap /= 2; //当gap为1时就直接插入排序//	for (j = 0; j < gap; j++) //通过j变量控制i变量,依次排序每个元素//	{//		for (i = j; i < n - gap; i += gap) // 排一组//		{//			int end = i;//			int tmp = a[end + gap];//			while (end >= 0)  // 排一个//			{//				if (a[end] > tmp)//				{//					a[end + gap] = a[end];//					end -= gap;//				}//				else//				{//					break;//				}//			}//			a[end + gap] = tmp;//		}//	}//}while (gap > 1){gap /= 2; //当gap为1时就直接插入排序for (i = 0; i < n - gap; i++) // 一个接一个排{int end = i;int tmp = a[end + gap];while (end >= 0)  // 排一个{if (a[end] > tmp){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}}
}

  每组的数据排序采用了直接插入排序,希尔排序的主要思想是需要进行多次预排序,使数据逐渐有序。

2.3 特性总结

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

3. 选择排序

3.1 基本思想

  每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完

3.2 代码实现

  这里进行了一点点小小的优化,同时找到最大和最小值,分别放到起始位置和末位置。

void Swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}//O(n*2)
void SelectSort(int* a, int n)
{int end = n - 1;int begin = 0;while (begin < end){int mini = begin, maxi = end;int i = 0;for (i = begin; i <= end; i++){if (a[i] < a[mini])mini = i;if (a[i] > a[maxi])maxi = i;}Swap(&a[mini], &a[begin]);if (begin == maxi)maxi = mini;Swap(&a[maxi], &a[end]);begin++;end--;}
}

  值得注意的是当 begin 与 maxi 重合的情况,因为我们是先交换最小值与首位置,交换完后就会导致 maxi 指向的值不再是最大值,而是跑到了交换后的 mini 所指向的地方,因此当 begin 与 maxi 重合时,在交换完后我们就需要修改 maxi 的位置,使其指向正确的位置。具体如图:
在这里插入图片描述

3.3 特性总结

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

4. 堆排序

4.1 基本思想

  堆排序是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。具体思路在我的另一篇文章数据结构篇六:二叉树中有所讲解,想了解的可以点击这里进行跳转,这里我就只贴上代码了。

4.2 代码实现

//向上调整
void AdjustUp(int* data, int child)
{int parent = (child - 1) / 2;while (child > 0){if (data[child] < data[parent]) // "<" 小堆{Swap(&data[child], &data[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}//向下调整
void AdjustDown(int* data, int size, int parent)
{int child = (parent * 2) + 1;while (child < size){if (child + 1 < size && data[child] > data[child + 1])  // "<" 大堆{child++;}if (data[child] < data[parent]) // ">"大堆{Swap(&data[child], &data[parent]);parent = child;child = (parent * 2) + 1;}else{break;}}
}//堆排序
//升序 -- 大堆
//降序 -- 小堆
void HeapSort(int* a, int n)
{1.建堆 O(N*logn)int i = 0;//for (i = 1; i < n; i++)//{//	AdjustUp(a, i);//}//2.建堆 O(N)for (i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}int end = n - 1;while (end >= 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);end--;}
}

4.3 特性总结

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

5. 冒泡排序

5.1 基本思想

  根据序列中两个数据的大小来对换这两个数据在序列中的位置。这个很简单,将每个数与其他数依次进行比较排序就完成了。

5.2 代码实现

void BubbleSort(int* a, int n)
{int i = 0;for (i = 0; i < n - 1; i++) //趟数{int j = 0;for (j = 0; j < n - 1 - i; j++) //每个元素比较次数{if (a[j] > a[j + 1]){Swap(&a[j], &a[j + 1]);}}}
}

5.3 特性总结

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

6. 快速排序

6.1 基本思想

  取待排序元素序列中的某元素作为基准值,按照该基准值将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

6.1.1 思想一

  这里我将介绍三种快速排序的思路,先看第一种。左子序列中需要所有元素均小于基准值,右子序列中需要所有元素均大于基准值,因此我们要在右边找小于基准值的移到左边,左边找大于基准值的移到右边。
在这里插入图片描述
在这里插入图片描述
  这里注意的是必须从右开始往左找,这样 left 最终停止的位置才能将 key 放到正确的位置上。
  这只是进行的一次排序,之后就是以它为分界点,对左右序列继续排序,是一个不断分割 + 排序的过程,因此我们可以考虑用递归来解决。

6.1.2 思想二

  是一个挖坑的思路,大体上与第一种相似,略有不同。
在这里插入图片描述

6.1.3 思想三

  用 prev 指向数据开头,cur 指向 prev 的下一个,再用 key 记录第一个数据当作基准值。 用 cur 从头开始找比 key 小的,找到了就与 prev 的下一位置进行交换,因为当前的 prev 指向的是基准值,我们是从基准值的下一个位置开始排序的,所以是与基准值的下一位置进行交换。本质上可以看作为 prev 前的数据包括 prev 指向的数据,都是比基准值小的,而完成这一效果的做法就是通过 cur 来找到比基准值小的数据,然后与prev 的下一个数据进行交换再进行 prev++,直到cur 遍历完数据为止。
在这里插入图片描述
  prev 之前的数据不包括 key 的位置,也就是从第二个数据开始到 prev 之间都是比 key 小的,而将此时的 prev 与 key 进行交换,就产生了从第一个数据开始到此时 prev 之前的数据都是比 key 小的了,说明这个数就排序完成了。

6.2 代码实现

int Partion1(int* a, int left, int right)
{int min = GetMidIndex(a, left, right);Swap(&a[left], &a[min]);int key = left;while (left < right){//从右往左,找小的while (left < right && a[right] >= a[key]){right--;}//从左往右,找大的while (left < right && a[left] <= a[key]){left++;}Swap(&a[left], &a[right]);}Swap(&a[left], &a[key]);return left;
}//挖坑法
int Partion2(int* a, int left, int right)
{int min = GetMidIndex(a, left, right);Swap(&a[left], &a[min]);int pivot = left;int key = a[left];while (left < right){//从右往左,找小的,放到左边的坑里while (left < right && a[right] >= key){right--;}//Swap(&a[pivot], &a[right]);a[pivot] = a[right];pivot = right;//从左往右,找大的,放到右边的坑里while (left < right && a[left] <= key){left++;}//Swap(&a[pivot], &a[left]);a[pivot] = a[left];pivot = left;}a[pivot] = key;return pivot;
}//前后指针
int Partion3(int* a, int left, int right)
{int key = left;int prev = left , cur = left + 1;while (cur <= right){if (a[key] > a[cur]){Swap(&a[cur], &a[++prev]);cur++;}else{cur++;}}Swap(&a[key], &a[prev]);return prev;
}

6.2.1 递归版本

  三个思想每次返回的都是基准值的排序好之后的下标,也就是分成了左右两个子区间,用递归不断对子区间进行排序就可以了,如果不太理解递归过程,可以去跳转到数据结构篇六:二叉树这篇文章去看一看二叉树前序遍历递归。

void QuickSort(int* a, int left, int right)
{if (left >= right)return;int key = Partion3(a, left, right);QuickSort(a, left, key - 1);QuickSort(a, key + 1, right);
}

6.2.2 非递归版本

  非递归的实现就需要借助栈的辅助了,我们将每一次需要进行排序的区间存入栈中,在每排序完一个子区间都会将这个子区间分成的更小的子区间进行压栈。每开始排序一个区间都会将该区间进行出栈,同时入栈这个区间的子区间,直到栈为空时说明所以区间都排序完成了。
在这里插入图片描述
  这样不断重复就相当于模拟了递归的过程。非递归的好处在于不需要消耗太多的空间,如果数据太多的话,递归深度过高容易造成栈溢出,非递归就很好的解决了这个问题。

void QuickSortNonR(int* a, int left, int right)
{Stack st;StackInit(&st);StackPush(&st, left); //入栈StackPush(&st, right);while (!StackEmpty(&st)){int end = StackTop(&st);//取栈顶元素StackPop(&st);          //出栈int begin = StackTop(&st);StackPop(&st);int key = Partion3(a, begin, end);//[begin,key - 1] key [key + 1,end]if (begin < key - 1){StackPush(&st, begin); //入栈StackPush(&st, key - 1);}if (key + 1 < end){StackPush(&st, key + 1);StackPush(&st, end);}}StackDestroy(&st);
}

6.3 优化

  大家看代码可以会看到一个GetMidIndex();这个函数,这是一个对与基准值选取的优化,假设第一个数据就是所有数据的最大值或者最小值,那么排序会出现什么情况?会出现第一次排序一直遍历完才找到,相较于两边同时找会慢上很多,因此才有了这个对 key 值选取的优化。

int GetMidIndex(int* a, int left, int right)
{int mid = (left + right) / 2;if (a[left] > a[mid]){if (a[mid] > a[right]){return mid;}else if(a[left] < a[right]){return left;}else{return right;}}else  //a[left] < a[mid]{if (a[right] < a[left]){return left;}else if (a[right] > a[mid]){return mid;}else{return right;}}
}

6.4 特性总结

  1. 快速排序整体的综合性能和使用场景都是比较好的一个排序,但是对于已经比较有序的数据排序比较慢,是属于一个数据越乱排序效果越好的一个算法。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(logN)
  4. 稳定性:不稳定

7.归并排序

7.1 基本思想

  归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已经有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
在这里插入图片描述
  本质上为两两排序,四四排序……,不断增加数据进行排序的过程。我们要做的就是不断分割区别,再对相邻的两个区间进行排序。由于本身的数据需要进行比较,因此还需要一个临时数组来保存每次排序好的数据,在结束排序后再拷贝回原数组就可以了。递归过程可参考数据结构篇六:二叉树这篇文章来理解。

7.2 代码实现

7.2.1 递归版本

void _MergeSort(int* a, int left, int right, int* tmp)
{if (left >= right)return;//进行递归分割区间int mid = (left + right) / 2;_MergeSort(a, left, mid, tmp);_MergeSort(a, mid + 1, right, tmp);//进行排序int begin1 = left, end1 = mid; //左区间int begin2 = mid + 1, end2 = right;//右区间int i = left;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[i++] = a[begin1++];}else{tmp[i++] = a[begin2++];}}while (begin1 <= end1){tmp[i++] = a[begin1++];}while (begin2 <= end2){tmp[i++] = a[begin2++];}}void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){printf("MergeSort:");exit(-1);}_MergeSort(a, 0, n - 1, tmp);free(tmp);tmp = NULL;
}

7.2.2 非递归版本

  非递归第一时间时间是结束栈或者队列来完成,但是这里却不行,因为我们需要将区间先不断分割到最小才开始排序,而在用栈和队列模拟递归时存储的是区间的边界,到栈或者队列为空时就结束了。但是在这里,我们在分割到最小的区间时,在排序完出队列就结束了。
在这里插入图片描述
  也就是这一步完成之后栈或者队列就为空了,就结束了,无法完成排序,所以不能用栈或者队列来辅助完成。
  因此就要换一个思路,这里是通过 gap 来控制区间,不需要进行递归分割,直接用循环进行控制区间,通过 gap 来更改每次区间的大小。
在这里插入图片描述
  与这个对照看更容易理解。
在这里插入图片描述
  但是这样就会出现一个大问题,gap 每次都是之前的二倍,也就是每次排序的数据个数都是之前的二倍,2,4,8,16……,那么如果是10个数据呢?就会出现越界情况,明明只有10个数据,你却访问到了10个数据后面的空间。因此还需要对这种情况进行调整,防止出现访问不该访问的地址空间的情况。
  通过观看代码知道,会出现越界情况的只有end1,begin2,end2这三个,因为一个区间没有数据的话循环根本就不会进来的,而有数据的话 begin1 就不会越界。end2 越界的话只需要将 end2 的值改为 n - 1 就可以了,让它重新指向最后一个数据的位置就不会出现越界了,begin2 越界的话将 begin2 改为 n,end2 改为 n - 1,这样 begin2 > end2 就说明该区间不存在了,就不会访问这个区间了。end1 越界的情况与 end2 越界的处理方法一致。这样就完美解决了会发生越界的问题了。

void MergeSortNonR1(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){printf("MergeSort:");exit(-1);}int gap = 1, i = 0;  //类似层序while (gap < n){for (i = 0; i < n; i = i + 2 * gap){//[i,i+gap-1]  [i+gap, i+gap*2-1]int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + gap * 2 - 1;int j = i;//end2 越界if (end2 >= n){end2 = n - 1;}//begin2 越界 [begin2,end2]不存在,讲这个区间设置为不存在if (begin2 >= n){begin2 = n;end2 = n - 1;}//end1 越界,[begin2,end2]不存在if (end1 >= n){end1 = n - 1;}while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[j++] = a[begin1++];}else{tmp[j++] = a[begin2++];}}while (begin1 <= end1){tmp[j++] = a[begin1++];}while (begin2 <= end2){tmp[j++] = a[begin2++];}}int j = 0;for (j = 0; j < n; j++){a[j] = tmp[j];}gap *= 2;}free(tmp);tmp = NULL;
}

7.3 特性总结

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

8. 计数排序

8.1 基本思想

  统计相同元素出现次数,根据统计的结果将结果放回到原来的序列中。
在这里插入图片描述
  可以理解为用新开辟出来的 count 数组来记录每个数据出现的次数,count 数组的下标代表原数组的数据,count 数组中存储的,就是所对应下标出现的次数。
  从上图看,0 和 1 是没有使用到的空间,有用的空间只有 2 到 9,因此我们可以用最大值减去最小值加一,来开辟count 数组的空间,减小空间损耗。但同时下标对应的原数组数据也会发生改变,比如上面的 2,在存放时就需要减去最小值 2 来存放到 0 这个位置,在最后放回原数组时再加上这个最小值就可以了。
  不好理解的话大家可以通过画图带入数据来更加细致的理解这个过程。这也是学习数据结构内容的重要方法。

8.2 代码实现

void CountSort(int* a, int n)
{int max = a[0], min = a[0];int i = 0;for (i = 0; i < n; i++){if (a[i] < min){min = a[i];}if (a[i] > max){max = a[i];}}int range = max - min + 1;int* count = (int*)malloc(sizeof(int) * range);if (count == NULL){printf("MergeSort:");exit(-1);}memset(count, 0, sizeof(int) * range);//计数for (i = 0; i < n; i++){count[a[i] - min]++;}//排序int j = 0;for (i = 0; i < range; i++){while (count[i]--){a[j++] = i + min;}}free(count);count = NULL;
}

8.3 特性总结

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

9. 总结

  此篇内容也是比较多,跟二叉树一样也是花费了几天时间来整理(叹气),不过还是比二叉树好多了(哎嘿)。如果大家发现有什么错误的地方,可以私信或者评论区指出喔(官话)。那么数据结构就先告一段落了,接下来就要进行C++和Linux的学习了,希望能与大家共同进步,那么本期就到此结束,让我们下期再见!!觉得不错可以点个赞以示鼓励喔!!

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

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

相关文章

LeetCode 33题:搜索旋转排序数组

目录 题目 思路 代码 暴力解法 分方向法 二分法 题目 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 …

提速Rust编译器!

Nethercote是一位研究Rust编译器的软件工程师。最近&#xff0c;他正在探索如何提升Rust编译器的性能&#xff0c;在他的博客文章中介绍了Rust编译器是如何将代码分割成代码生成单元&#xff08;CGU&#xff09;的以及rustc的性能加速。 他解释了不同数量和大小的CGU之间的权衡…

考虑微网新能源经济消纳的共享储能优化配置(Matlab代码实现

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

VSCode Remote-SSH (Windows)

1. VSCode 安装 VSCode 2. 安装扩展 Remote SSH Getting started Follow the step-by-step tutorial or if you have a simple SSH host setup, connect to it as follows: Press F1 and run the Remote-SSH: Open SSH Host… command.Enter your user and host/IP in the …

Sui网络的稳定性和高性能

Sui的最初的协议开发者设计了可扩展的网络&#xff0c;通过水平扩展的方式来保持可负担得起的gas费用。其他区块链与之相比&#xff0c;则使用稀缺性和交易成本来控制网络活动。 Sui主网上线前90天的数据指标证明了这一设计概念&#xff0c;在保持100&#xff05;正常运行的同…

实现Jenkins自动发包配置

参考抖音&#xff1a;Java不良人 其中的视频演示代码 不推荐把jenkins端口一直开放&#xff0c;推荐使用时候放开&#xff08;版本不太新&#xff0c;避免漏洞攻击&#xff09; [rootVM-4-12-centos soft]# docker-compose -v Docker Compose version v2.19.1docker-compose.…

提升效率!云原生生态从业人员不可或缺的工具集合!

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

​ATF(TF-A)安全通告 TFV-7 (CVE-2018-3639)​

ATF(TF-A)安全通告汇总 目录 一、ATF(TF-A)安全通告 TFV-7 (CVE-2018-3639) 二、静态缓解&#xff08;Static mitigation&#xff09; 三、动态缓解&#xff08;Dynamic mitigation&#xff09; 一、ATF(TF-A)安全通告 TFV-7 (CVE-2018-3639) Title TF-A披露基于cache前瞻…

92. 反转链表 II

92. 反转链表 II 题目-中等难度示例1. 获取头 反转中间 获取尾 -> 拼接2. 链表转换列表 -> 计算 -> 转换回链表 题目-中等难度 给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right 。请你反转从位置 left 到位置 right 的链表节点…

请求转发和请求重定向

目录 1. 定义层面 2. 请求方层面 3. 数据共享层面 4. 最终 url 层面 5. 代码实现层面 请求转发 请求重定向 在Java中&#xff0c;跳转网页的方式有两种&#xff0c;一种是请求转发&#xff0c;另一种是请求重定向&#xff0c;而实际上&#xff0c;这两种方式是有着明显…

JavaScript数据结构【进阶】

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录 使用 splice() 添加元素使用 slice() 复制数组元素使用展开运算符复制数组使用展开运算符合并数组使用 indexOf() 检查元素是否存在使用 for 循环遍历数组中的全部元素创建复杂的多维数组将键值对添加到对象…

APP外包开发的H5开发框架

跨平台移动应用开发框架允许开发者使用一套代码在多个操作系统上构建应用程序&#xff0c;从而节省时间和资源。以下是一些常见的跨平台移动应用开发框架以及它们的特点&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0…

从源码层面深度剖析Spring循环依赖 | 京东云技术团队

以下举例皆针对单例模式讨论 图解参考 https://www.processon.com/view/link/60e3b0ae0e3e74200e2478ce 1、Spring 如何创建Bean&#xff1f; 对于单例Bean来说&#xff0c;在Spring容器整个生命周期内&#xff0c;有且只有一个对象。 Spring 在创建 Bean 过程中&#xff0…

Vue在页面输出JSON对象,测试接口可复制使用

效果图&#xff1a; 数据处理前&#xff1a; 数据处理后&#xff1a; 代码实现&#xff1a; HTML: <el-table height"600" :data"tableData" border style"width: 100%" tooltip-effect"dark" size"mini"><el-…

前后端分离------后端创建笔记(上)

本文章转载于【SpringBootVue】全网最简单但实用的前后端分离项目实战笔记 - 前端_大菜007的博客-CSDN博客 仅用于学习和讨论&#xff0c;如有侵权请联系 源码&#xff1a;https://gitee.com/green_vegetables/x-admin-project.git 素材&#xff1a;https://pan.baidu.com/s/…

如何使用 ESP-01S 模块

如何使用 ESP-01S 模块 原始PDF文档 参考&#xff1a; 将 ESP-01 用作 WiFi shield的更好方法 (e-tinkers.com) How do I use ESP8266 ESP-01S WiFi Module with ESP-01S Adapter - Using Arduino / Programming Questions - Arduino Forum ESP-01S WiFi 模块 – 配置布线 -…

服务管理和计划任务

文章目录 服务管理计划任务 服务管理 systemctl 命令字 服务名 //配置服务与systemctl有关的命令字&#xff1a; 计划任务 一次性计划 at 时间 at now 5 min //当前时间五分钟后执行 at -l //列出计划任务 atrm 任务号 //删除计划任务执行完命令后Ctrld生效 周期性计…

Oracle数据迁移

问题描述&#xff1a; oracle数据库的所有表结构、数据、索引等需要需从测试库迁移到正式库。 解决步骤&#xff1a; oracle数据库迁移&#xff0c;主要通过expdp从测试库所在的源服务器将指定的数据表或数据源导出为一个或多个数据文件&#xff08;.dmp文件&#xff09;&…

添加SQLCipher 到项目中

文章目录 一、克隆下载SQLCipher二、手动导入1. 生成sqlite3.c2. 在项目中添加命令3. 添加 Security.framework 三、CocoaPods导入 SQLCipher官方地址 一、克隆下载SQLCipher $ cd ~/Documents/code $ git clone https://github.com/sqlcipher/sqlcipher.git二、手动导入 1.…

电商系统架构设计系列(八):订单数据越来越多,数据库越来越慢该怎么办?

上篇文章中&#xff0c;我给你留了一个思考题&#xff1a;订单数据越来越多&#xff0c;数据库越来越慢该怎么办&#xff1f; 今天这篇文章&#xff0c;我们来聊一下如何应对数据的持续增长&#xff0c;特别是像订单数据这种会随着时间一直累积的数据。 引言 为什么数据量越大…