【初阶数据结构】11.排序(2)

文章目录

    • 2.3 交换排序
      • 2.3.1 冒泡排序
      • 2.3.2 快速排序
        • 2.3.2.1 hoare版本
        • 2.3.2.2 挖坑法
        • 2.3.2.3 lomuto前后指针
        • 2.3.2.4 非递归版本
    • 2.4 归并排序
    • 2.5 测试代码:排序性能对比
    • 2.6 非比较排序
      • 2.6.1 计数排序
  • 3.排序算法复杂度及稳定性分析


2.3 交换排序

交换排序基本思想:

所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置

交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动

2.3.1 冒泡排序

前面在算法题中我们已经接触过冒泡排序的思路了,冒泡排序是一种最基础的交换排序。之所以叫做冒泡排序,因为每一个元素都可以像小气泡一样,根据自身大小一点一点向数组的一侧移动。

90220543959701b95a30f985f38ce559

代码实现:

void BubbleSort(int* a, int n) 
{int exchange = 0;for (int i = 0; i < n; i++){for (int j = 0; j <n-i-1 ; j++){if (a[j] > a[j + 1]) {exchange = 1;swap(&a[j], &a[j + 1]);}}if (exchange == 0) {break;}}
}

冒泡排序的特性总结

  1. 时间复杂度:O(N2 )
  2. 空间复杂度:O(1)

2.3.2 快速排序

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

快速排序实现主框架:

//快速排序
void QuickSort(int* a, int left, int right) 
{if (left >= right) {return;}//_QuickSort用于按照基准值将区间[left,right)中的元素进行划分int meet = _QuickSort(a, left, right);QuickSort(a, left, meet - 1);QuickSort(a, meet + 1, right);
}

将区间中的元素进行划分的 _QuickSort 方法主要有以下几种实现方式:

2.3.2.1 hoare版本

算法思路 :

  1. 创建左右指针,确定基准值

  2. right从右向左找出比基准值小的数据,left从左向右找出比基准值大的数据,左右指针数据交换,进入下次循环

问题1:为什么跳出循环后right位置的值一定不大于key

left > right 时,即right走到left的左侧,而left扫描过的数据均不大于key,因此right此时指向的数据一定不大于key

f802ebaaee1af75ab282651dcffa92bf

问题2:为什么leftright指定的数据和key值相等时也要交换?

相等的值参与交换确实有一些额外消耗。实际还有各种复杂的场景,假设数组中的数据大量重复时,无法进行有效的分割排序。

a1c57dd3e7292dd607123c05b75540ce

key是基准值


例如:

6 1 2 7 9 3

key指向6,left指向1,right指向3

left从左向右找出比基准值大的数据,right从右向左找出比基准值小的数据,左右指针数据交换,进入下次循环

left指向7,right指向3

交换leftright

left指向的数据7变成数据3,right指向的数据3变成数据7


6 1 2 3 9 7

key指向6,left指向3,right指向7

left从左向右找出比基准值大的数据,right从右向左找出比基准值小的数据,左右指针数据交换,进入下次循环

left指向9,right指向3

此时left>right

left > right的时候,right和基准值key交换

交换keyright的数据

3 1 2 6 9 7

然后返回基准值6

代码实现:

Sort.h

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<time.h>//打印
void PrintArr(int* arr, int n);
//快速排序
//hoare版本
void QuickSort(int* arr, int left, int right);

Sort.c

#include"Sort.h"//打印
void PrintArr(int* arr, int n)
{for (int i = 0; i < n; i++){printf("%d ", arr[i]);}printf("\n");
}//交换
void Swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}//快速排序找基准值
int _QuickSort(int* arr, int left, int right)
{int keyi = left;++left;while (left <= right)//left和right相遇的位置的值比基准值要大{//right找到比基准值小的while (left <= right && arr[right] > arr[keyi]){right--;//right从右往左找比基准值keyi要小的,当前位置不满足就向前一位}//left找到比基准值大的while (left <= right && arr[left] < arr[keyi]){left++;//left从左往右找比基准值keyi要大的,当前位置不满足就向后一位}//right和left互相交换//因为left找到比基准值大的,right找到比基准值小的//left比right小的时候,自然要交换数据if (left <= right){Swap(&arr[left++], &arr[right--]);}}//left > right的时候,right和基准值keyi交换Swap(&arr[keyi], &arr[right]);return right;//返回基准值
}//快速排序
void QuickSort(int* arr, int left, int right)
{if (left >= right){return;}//[left,right]--->找基准值keyiint keyi = _QuickSort(arr, left, right);//左子序列:[left,keyi-1]QuickSort(arr, left, keyi - 1);//右子序列:[keyi+1,right]QuickSort(arr, keyi + 1, right);
}

test.c

#include"Sort.h"int main()
{int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };int n = sizeof(a) / sizeof(int);printf("排序前:");PrintArr(a, n);QuickSort(a, 0, n - 1);printf("排序后:");PrintArr(a, n);return 0;
}

2.3.2.2 挖坑法

思路:

创建左右指针。首先从右向左找出比基准小的数据,找到后立即放入左边坑中,当前位置变为新的坑,然后从左向右找出比基准大的数据,找到后立即放入右边坑中,当前位置变为新的坑,结束循环后将最开始存储的分界值放入当前的坑中,返回当前坑下标(即分界值下标)

7f127d97b2d93319bb37d19159e3094e

代码:

Sort.h

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<time.h>//打印
void PrintArr(int* arr, int n);
//快速排序
//挖坑法
void QuickSort(int* arr, int left, int right);

Sort.c

#include"Sort.h"//打印
void PrintArr(int* arr, int n)
{for (int i = 0; i < n; i++){printf("%d ", arr[i]);}printf("\n");
}//快速排序找基准值
//挖坑法
int _QuickSort2(int* arr, int left, int right)
{int hole = left;//找到坑位int key = arr[hole];//存储第一个坑位的值while (left < right)//left = right就跳出循环{while (left < right && arr[right] > key)//找到arr[right] < key的right{--right;}arr[hole] = arr[right];//填坑hole = right;//确定新的坑位while (left < right && arr[left] < key)//找到arr[left] > key的left{++left;}arr[hole] = arr[left];//填坑hole = left;//确定新的坑位}arr[hole] = key;//最后的坑位放上初始值return hole;//返回基准值
}//快速排序
void QuickSort(int* arr, int left, int right)
{if (left >= right){return;}//[left,right]--->找基准值keyiint keyi = _QuickSort(arr, left, right);//左子序列:[left,keyi-1]QuickSort(arr, left, keyi - 1);//右子序列:[keyi+1,right]QuickSort(arr, keyi + 1, right);
}

test.c

#include"Sort.h"int main()
{int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };int n = sizeof(a) / sizeof(int);printf("排序前:");PrintArr(a, n);QuickSort(a, 0, n - 1);printf("排序后:");PrintArr(a, n);return 0;
}

当然这里会有一点小问题的。比如数据是升序的时候,基准值找起来会有点问题。但因为这里是初阶数据结构,所以先不讨论基准值。后面的高阶数据结构里面会讲三数取中


2.3.2.3 lomuto前后指针

创建前后指针,从左往右找比基准值小的进行交换,使得小的都排在基准值的左边。

1758c026cc6d45644b6526fe9ac5dd67

思路:

定义两个变量prevcur

cur指向位置的数据和key值比较

arr[cur] < arr[keyi]prev往后走一步并和cur交换

arr[cur] > arr[keyi]cur继续往后

arr[cur]越界后,将key里的值和arr[prev]的值互换。

代码:

Sort.h

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<time.h>//打印
void PrintArr(int* arr, int n);
//快速排序
//lomuto前后指针法
void QuickSort(int* arr, int left, int right);

Sort.c

#include"Sort.h"//打印
void PrintArr(int* arr, int n)
{for (int i = 0; i < n; i++){printf("%d ", arr[i]);}printf("\n");
}//交换
void Swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}//快速排序找基准值
//lomuto前后指针法
int _QuickSort3(int* arr, int left, int right)
{int prev = left, cur = left + 1;int keyi = left;while (cur <= right)//cur > right就说明越界了{if (arr[cur] < arr[keyi] && ++prev != cur)//若arr[cur] < arr[keyi],prev往后走一步并和cur交换{Swap(&arr[cur], &arr[prev]);}cur++;//若arr[cur] > arr[keyi],cur继续往后}Swap(&arr[keyi], &arr[prev]);//arr[cur]越界后,将key里的值和arr[prev]的值互换。return prev;//返回基准值
}//快速排序
void QuickSort(int* arr, int left, int right)
{if (left >= right){return;}//[left,right]--->找基准值keyiint keyi = _QuickSort(arr, left, right);//左子序列:[left,keyi-1]QuickSort(arr, left, keyi - 1);//右子序列:[keyi+1,right]QuickSort(arr, keyi + 1, right);
}

test.c

#include"Sort.h"int main()
{int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };int n = sizeof(a) / sizeof(int);printf("排序前:");PrintArr(a, n);QuickSort(a, 0, n - 1);printf("排序后:");PrintArr(a, n);return 0;
}

快速排序特性总结:

  1. 时间复杂度:O(nlogn)
  2. 空间复杂度:O(logn)

2.3.2.4 非递归版本

非递归版本的快速排序需要借助数据结构:栈

思路:

假设我们初始要排序的数是:5 3 9 6 2 4

经过一轮快速排序后,找到的基准值是6,也就是keyi=3

通过一轮快速排序后,接下来要排序的数是2 3 4 5 6 9


如果left=0,right=5,keyi=3

左区间:[left,keyi-1] [0,2]

右区间:[keyi+1,right] [4,5]

先把右区间的right5入栈,然后把右区间的left4入栈

然后把左区间的right2入栈,然后把左区间的left0入栈


此时栈里面是:0 2 4 5


然后出栈两次,取到left=0,right=2

我们对下标为0-2的数找基准值

找到基准值keyi=0


此时left=0,right=2,keyi=1

左区间:[left,keyi-1] [0,-1] 非有效区间,不入栈

右区间:[keyi+1,right] [1,2]

先把右区间的right2入栈,然后把右区间的left1入栈


此时栈里面是:1 2 4 5


然后出栈两次,取到left=1,right=2

我们对下标为1-2的数找基准值

找到基准值keyi=1


此时left=1,right=2,keyi=1

左区间:[left,keyi-1] [1,0] 非有效区间,不入栈

右区间:[keyi+1,right] [2,2] 非有效区间,不入栈


此时栈里面是:4 5


然后出栈两次,取到left=4,right=5

我们对下标为4-5的数找基准值

找到基准值keyi=4


此时left=4,right=5,keyi=4

左区间:[left,keyi-1] [4,3] 非有效区间,不入栈

右区间:[keyi+1,right] [5,5] 非有效区间,不入栈


此时排完序了,是2 3 4 5 6 9

我们销毁函数栈。

代码实现:

Sort.h

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<time.h>
#include <stdbool.h>
#include <assert.h>//打印
void PrintArr(int* arr, int n);
//非递归版本快排
//--借助数据结构--栈
void QuickSortNonR(int* arr, int left, int right);

Sort.c

#include"Sort.h"//打印
void PrintArr(int* arr, int n)
{for (int i = 0; i < n; i++){printf("%d ", arr[i]);}printf("\n");
}//定义栈的结构
typedef int STDataType;
typedef struct Stack
{STDataType* arr;int capacity;     //栈的空间大小int top;          //栈顶
}ST;//栈的初始化
void STInit(ST* ps)
{assert(ps);ps->arr = NULL;ps->capacity = ps->top = 0;
}//栈顶---入数据,出数据
//入数据
void StackPush(ST* ps, STDataType x)
{assert(ps);//1.判断空间是否足够if (ps->capacity == ps->top){int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;STDataType* tmp = (STDataType*)realloc(ps->arr, newCapacity * sizeof(STDataType));if (tmp == NULL){perror("realloc fail!");exit(1);}ps->arr = tmp;ps->capacity = newCapacity;}//空间足够ps->arr[ps->top++] = x;
}//判断栈是否为空
bool StackEmpty(ST* ps)
{assert(ps);return ps->top == 0;
}//栈顶出数据
void StackPop(ST* ps)
{assert(ps);assert(!StackEmpty(ps));--ps->top;
}//取栈顶元素
STDataType StackTop(ST* ps)
{assert(ps);assert(!StackEmpty(ps));return ps->arr[ps->top - 1];
}//栈的销毁
void STDestroy(ST* ps)
{assert(ps);if (ps->arr)free(ps->arr);ps->arr = NULL;ps->top = ps->capacity = 0;
}//非递归版本快排
//--借助数据结构--栈
void QuickSortNonR(int* arr, int left, int right)
{ST st;//创建栈STInit(&st);//栈的初始化StackPush(&st, right);//栈顶入数据StackPush(&st, left);//栈顶入数据while (!StackEmpty(&st))//栈不为空就进入循环{//取栈顶元素---取两次int begin = StackTop(&st);StackPop(&st);//栈顶出数据int end = StackTop(&st);StackPop(&st);//栈顶出数据//[begin,end]---找基准值//双指针法int prev = begin;int cur = begin + 1;int keyi = begin;while (cur <= end){if (arr[cur] < arr[keyi] && ++prev != cur)//这里<和<=一样{Swap(&arr[cur], &arr[prev]);}cur++;}Swap(&arr[keyi], &arr[prev]);//上面循环结束说明cur越界了keyi = prev;//根据基准值keyi划分左右区间//左区间:[begin,keyi-1]//右区间:[keyi+1,end]if (keyi + 1 < end){StackPush(&st, end);//右区间入栈StackPush(&st, keyi + 1);//左区间入栈}if (keyi - 1 > begin){StackPush(&st, keyi - 1);//右区间入栈StackPush(&st, begin);//左区间入栈}}STDestroy(&st);//销毁栈
}

test.c

#include"Sort.h"int main()
{int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };int n = sizeof(a) / sizeof(int);printf("排序前:");PrintArr(a, n);QuickSortNonR(a, 0, n - 1);printf("排序后:");PrintArr(a, n);return 0;
}

2.4 归并排序

归并排序算法思想:

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

45fedca89a7046dfaa8bd24a4bb1962d

思路:

数据:10 6 7 1 3 9 47个元素,下标0-6


定义left,right,mid三个变量

left指向下标0的元素

right指向下标6的元素

mid=(left+right)/2


[left,mid]:10 6 7 1

[mid+1,right]:3 9 4


然后可以继续像上面这样分,直到全部分成一个一个的数据。

递归的话也是递归到这个时候结束,然后往上返回。

12653da879b1489b83c1bee59b5dfe19

那么,对于10 6这两个数字组成的数组,我们怎么把他排序成6 10呢?


left指向下标0的元素

right指向下标1的元素

mid=(left+right)/2=0


[left,mid]:[0,0] 10

[mid+1,right]:[1,1] 6

其他几个数据也一样

然后我们新创建一个数组来存放这些数:6 10, 1 7, 3 9, 4

然后往上返回。

ff57a163facec37b1c08ffb00046f857

然后我们新创建一个数组来存放这些数:6 10 1 7, 3 9 4

然后往上返回。

4815ea9722df416e7c9556c5d54bcd0e

然后把tmp中的数据拷贝回arr

销毁tmp

代码:

Sort.h

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<time.h>
#include <stdbool.h>
#include <assert.h>//打印
void PrintArr(int* arr, int n);
//归并排序
void MergeSort(int* arr, int n);

Sort.c

#include"Sort.h"//打印
void PrintArr(int* arr, int n)
{for (int i = 0; i < n; i++){printf("%d ", arr[i]);}printf("\n");
}//归并排序
void _MergeSort(int* arr, int left, int right, int* tmp)
{if (left >= right)//分解到只有一个数据的时候就结束了{return;}int mid = (left + right) / 2;//中间下标//[left,mid] [mid+1,right]//不断二分_MergeSort(arr, left, mid, tmp);_MergeSort(arr, mid + 1, right, tmp);//合并//[left,mid] [mid+1,right]//表示两个区间int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;int index = begin1;//定义tmp数组的下标while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] < arr[begin2])//比较arr[begin1] 和 arr[begin2]的大小{tmp[index++] = arr[begin1++];//先往tmp里面插入小的}else {tmp[index++] = arr[begin2++];//然后往tmp里面插入大的}}//跳出循环:要么begin1越界  要么begin2越界while (begin1 <= end1)//begin2越界,begin1没有越界{tmp[index++] = arr[begin1++];//继续插入}while (begin2 <= end2)//begin1越界,begin2没有越界{tmp[index++] = arr[begin2++];}//[left,mid] [mid+1,right]//把tmp中的数据拷贝回arr中for (int i = left; i <= right; i++){arr[i] = tmp[i];}
}void MergeSort(int* arr, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);//合并的放在新创建的新数组里,大小是原来数组的空间_MergeSort(arr, 0, n - 1, tmp);free(tmp);//使用完了要释放掉空间
}

test.c

#include"Sort.h"int main()
{int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };int n = sizeof(a) / sizeof(int);printf("排序前:");PrintArr(a, n);MergeSort(a, n);printf("排序后:");PrintArr(a, n);TestOP();return 0;
}

归并排序特性总结:

  1. 时间复杂度:O(nlogn)
  2. 空间复杂度:O(n)

2.5 测试代码:排序性能对比

// 测试排序的性能对比
void TestOP() 
{ srand(time(0)); const int N = 100000; int* a1 = (int*)malloc(sizeof(int)*N); int* a2 = (int*)malloc(sizeof(int)*N); int* a3 = (int*)malloc(sizeof(int)*N); int* a4 = (int*)malloc(sizeof(int)*N); int* a5 = (int*)malloc(sizeof(int)*N); int* a6 = (int*)malloc(sizeof(int)*N); int* a7 = (int*)malloc(sizeof(int)*N); for (int i = 0; i < N; ++i) { a1[i] = rand(); a2[i] = a1[i]; a3[i] = a1[i]; a4[i] = a1[i]; a5[i] = a1[i]; a6[i] = a1[i];a7[i] = a1[i]; } int begin1 = clock(); InsertSort(a1, N); int end1 = clock(); int begin2 = clock(); ShellSort(a2, N); int end2 = clock(); int begin3 = clock(); SelectSort(a3, N); int end3 = clock(); int begin4 = clock(); HeapSort(a4, N); int end4 = clock(); int begin5 = clock(); QuickSort(a5, 0, N-1); int end5 = clock(); int begin6 = clock(); MergeSort(a6, N); int end6 = clock(); int begin7 = clock(); BubbleSort(a7, N); int end7 = clock();printf("InsertSort:%d\n", end1 - begin1); printf("ShellSort:%d\n", end2 - begin2);printf("SelectSort:%d\n", end3 - begin3); printf("HeapSort:%d\n", end4 - begin4); printf("QuickSort:%d\n", end5 - begin5); printf("MergeSort:%d\n", end6 - begin6); printf("BubbleSort:%d\n", end7 - begin7); free(a1); free(a2); free(a3); free(a4); free(a5); free(a6); free(a7); 
} 

对于上面程序的测试结果:(单位:ms

InsertSort:3816
ShellSort:13
SelectSort:4253
HeapSort:15
QuickSort:9
MergeSort:12
BubbleSort:22812

2.6 非比较排序

2.6.1 计数排序

计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤:

  1. 统计相同元素出现次数

  2. 根据统计的结果将序列回收到原来的序列中

思路:

例如:{6,1,2,9,4,2,4,1,4}9个数

  1. 统计相同元素出现次数

6出现1次,1出现2次,2出现2次,9出现1次,4出现3

  1. 根据统计的结果将序列回收到原来的序列中

fca1a4c8a3578dc560a5c70715dbf4f6

定义一个i来遍历,i表示数组内元素

i先到0的位置,里面没有数据,不打印

然后到1的位置,里面有2,打印2

依次类推

最后打印结果:1 1 2 2 4 4 4 6 9


新数组的大小怎么确定?

利用原数组中最大的值来确定。创建max+1个空间的数组。

但这就会出现问题:比如有负数怎么办?比较{3, 4, 10000}呢?

如果把负数变成正数,取绝对值呢?也不行。因为正负数绝对值一样的情况下无法区分正负数。

那如果我们把负数统一加一个正数,让他变成整数呢?

我们可以让count=max-min+1

对于{-5,-5,9,5,1}

count=9-(-5)+1=15

创建数组,数组大小为15,下标为0-14

-5放在数组下标为0的位置中,这个位置元素为2

依此类推,下标-5就是原来的元素,这些元素和下标形成了映射关系。

fa7b858e7ef858bc2913b63473724b91

代码:

Sort.h

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<time.h>
#include <assert.h>
#include <stdbool.h>
#include <memory.h>//打印
void PrintArr(int* arr, int n);//计数排序
void CountSort(int* arr, int n);

Sort.c

#include "Sort.h"//打印
void PrintArr(int* arr, int n)
{for (int i = 0; i < n; i++){printf("%d ", arr[i]);}printf("\n");
}//计数排序
void CountSort(int* arr, int n)
{//根据最大值最小值确定数组大小int max = arr[0], min = arr[0];for (int i = 1; i < n; i++){if (arr[i] > max){max = arr[i];}if (arr[i] < min){min = arr[i];}}int range = max - min + 1;//确定数组元素个数int* count = (int*)malloc(sizeof(int) * range);//创建数组//判断不为空if (count == NULL){perror("malloc fail!");exit(1);}//初始化count数组中所有的数据为0memset(count, 0, range * sizeof(int));//count的大小是range//例如{100,101,109,105,101,105}//min = 100,arr[i] - min就是count里面的下标//统计数组中每个数据出现的次数for (int i = 0; i < n; i++){count[arr[i] - min]++;//这里的++是为了把次数放进去}//取count中的数据,往arr中放int index = 0;for (int i = 0; i < range; i++){while (count[i]--)//这里是为了把上面count对应次数给传进arr{arr[index++] = i + min;//表示arr数组的下标从0开始,存放数据}}
}

test.c

#include "Sort.h"int main()
{int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };int n = sizeof(a) / sizeof(int);printf("排序前:");PrintArr(a, n);CountSort(a, n);printf("排序后:");PrintArr(a, n);return 0;
}

计数排序的特性:

计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。 而且只能适用于整数排序,无法对小数排序。

时间复杂度:O(N + range)

空间复杂度:O(range)

稳定性:稳定


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

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

比如:6 2 4 9 1 2

排序后:1 2 2 4 6 9

一开始有两个2,一个在前面,一个在后面。

排序后也有两个2,一个在前面,一个在后面。

如果排序后前面的2还是在后面的2的前面,就称相对次序保持不变。称这种排序算法是稳定的;否则称为不稳定的。

e7b8b43a4d13a606ecdad3ae8021da6a

a07c89bf10f6b75bae782786b19431ff

稳定性验证案例

  1. 直接选择排序:5 8 5 2 9

  2. 希尔排序:5 8 2 5 9

  3. 堆排序:2 2 2 2

  4. 快速排序:5 3 3 4 3 8 9 10 11

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

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

相关文章

【包邮送书】码农职场:IT人求职就业手册

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。关…

遗传算法与深度学习实战——进化深度学习

遗传算法与深度学习实战——进化深度学习 0. 前言1. 进化深度学习1.1 进化深度学习简介1.2 进化计算简介 2. 进化深度学习应用场景3. 深度学习优化3.1 优化网络体系结构 4. 通过自动机器学习进行优化4.1 自动机器学习简介4.2 AutoML 工具 5. 进化深度学习应用5.1 模型选择&…

鸿蒙开发——axios封装请求、拦截器

描述&#xff1a;接口用的是PHP&#xff0c;框架TP5 源码地址 链接&#xff1a;https://pan.quark.cn/s/a610610ca406 提取码&#xff1a;rbYX 请求登录 HttpUtil HttpApi 使用方法

Qt基础 | 主机信息查询 | QHostInfo的介绍和使用 | QNetworkInterface的介绍和使用

文章目录 一、Qt 网络模块介绍二、主机信息查询1.QHostlnfo 和 QNetworkInterface 类2.QHostlnfo 的使用2.1 获取本机主机名和 IP 地址2.2 查找主机的地址信息 3.QNetworkInterface 的使用 Qt 网络模块&#xff1a; Qt基础 | 主机信息查询 | QHostInfo的介绍和使用 | QNetworkI…

常见的jmeter面试题及答案

1、解释什么是JMeter? JMeter是一款Java开源工具&#xff0c; 用于性能负载测试。它旨在分析和衡量Web应用程序和各种服务的性能和负载功能行为。 2、说明JMeter的工作原理? JMeter就像一群将请求发送到目标服务器的用户-样。它收集来自目标服务器的响应以及其他统计数据&…

python爬虫【3】—— 爬虫反反爬

一、常见的反爬手段和解决方法 二、splash 介绍与安装 三、验证码识别 图片验证码的处理方案 手动输入(input) 这种方法仅限于登录一次就可持续使用的情况图像识别引擎解析 使用光学识别引擎处理图片中的数据&#xff0c;目前常用于图片数据提取&#xff0c;较少用于验证码…

彻底搞懂jdk1.8中的haspMap原理(源码解析+ 对比jdk1.7)

彻底搞懂jdk1.8中的haspMap原理&#xff08;源码解析 对比jdk1.7&#xff09;_jdk1.8 hashmap效果演示-CSDN博客文章浏览阅读484次。前言&#xff1a;本博客只对jdk1.7中的hashMap进行文字性的说明&#xff0c;源码的说明只针对jdk1.8&#xff0c;因为现在开发多数都是jdk1.8.一…

【简单图解 最强计网八股】HTTP 和 HTTPS 的区别

HTTP&#xff08;HyperText Transfer Protocol 超文本传输协议&#xff09; HTTPS&#xff08;Hypertext Transfer Protocol Secure&#xff0c;超文本传输安全协议&#xff09; 通过 传输内容加密 和 身份认证 保证了传输过程的安全性 协议传输内容加密身份认证响应效率端口号…

IP Fabric三层路由

IP Fabric指的是在IP网络基础上建立起来的Overlay隧道技术。即为基于胖树的SpineLeaf拓扑结构的IP Fabric组网图。 在这种组网方式中&#xff0c;任何两台服务器间的通信不超过3台设备&#xff0c;每个Spine和Leaf节点全互连&#xff0c;可以方便地通过扩展Spine节点来实现网络…

Orcale(备份导入导出)

1.备份恢复 1.1.备份定义 备份就是把数据库复制到转储设备的过程。其中&#xff0c;转储设备是指用于放置数据库副本的磁带或磁盘。通常也将存放于转储设备中的数据库的副本称为原数据库的备份或转储。备份是一份数据副本 1.2.备份分类 从物理与逻辑的角度来分类&#xff1a…

在docker中安装MongoDB 5.0+

文章目录 1、查看物理机是否支持avx指令集&#xff1a;安装资料中的cpu-z_2.10-cn.exe&#xff0c;并打开2、查看虚拟机是否支持avx指令集&#xff1a;3、创建目录4、使用Docker来运行一个MongoDB数据库实例5、进入容器6、查看当前db版本7、查看当前db的链接机器地址8、帮助指令…

浮点数的二进制表示

浮点数的二进制表示 浮点数在C/C中对应 float 和 double 类型&#xff0c;我们有必要知道浮点数在计算机中实际存储方式。 IEEE754规定&#xff1a; 单精度浮点数字长32位&#xff0c;尾数长度23&#xff0c;指数长度8,指数偏移量127&#xff1b;双精度浮点数字长64位&#xf…

达梦数据库DPI 实现两个数据库数据互通

链接字符串是目标访问链接 目标访问用户名 口令实现 31 里访问33库的数据 如果在31上建立视图访问33的某个表 AS SELECT SZZJ.sys_user.id FROM SZZJ.sys_userszzj31_szzj33;

研0 冲刺算法竞赛 day25 P1223 排队接水

P1223 排队接水 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 考点&#xff1a;贪心算法 思路&#xff1a;很简单&#xff0c;快的先接水即可&#xff0c;要注意重复项 代码: #include<iostream> #include<algorithm> using namespace std;int arr[1000005];…

C语言进阶 10. 字符串

C语言进阶 10. 字符串 文章目录 C语言进阶 10. 字符串10.1. 字符串10.2. 字符串变量10.3. 字符串输入输出10.4. 字符串数组10.5. 单字符输入输出10.6. 字符串函数strlen()10.7. 字符串函数strc()10.8. 字符串函数strcpy()10.9. 字符串搜索函数10.10. PAT10-0. 说反话 (20)10-1.…

七天打造一套量化交易系统:Day6-人工智能在量化投资中的应用

七天打造一套量化交易系统&#xff1a;Day6-人工智能在量化投资中的应用 步骤一&#xff1a;数据获取步骤二&#xff1a;对股票样本进行初步处理步骤三&#xff1a;遗传算法选股遗传算 kmeans 类的主要代码 步骤四&#xff1a;回测结果 遗传算法是一种基础的人工智能算法&#…

CSS实现图片边框酷炫效果

一、前言 我们在浏览一些网页时&#xff0c;经常会看到一些好看酷炫的元素边框效果&#xff08;如下图&#xff09;&#xff0c;那么这些效果是怎么实现的呢&#xff1f;我们知道&#xff0c;一般的边框&#xff0c;要么是实线&#xff0c;要么是虚线&#xff08;点状&#xf…

快速识别音频文件转成文字

一、SenseVoice概述 阿里云通义千问开源了两款语音基座模型 SenseVoice&#xff08;用于语音识别&#xff09;和 CosyVoice&#xff08;用于语音生成&#xff09;。 SenseVoice 专注于高精度多语言语音识别、情感辨识和音频事件检测&#xff0c;有以下特点&#xff1a; 多语言…

spark 3.0.0源码环境搭建

环境 Spark版本&#xff1a;3.0.0 java版本&#xff1a;1.8 scala版本&#xff1a;2.12.19 Maven版本&#xff1a;3.8.1 编译spark 将spark-3.0.0的源码导入到idea中 执行mvn clean package -Phive -Phive-thriftserver -Pyarn -DskipTests 执行sparksql示例类SparkSQLExam…

机器学习算法——常规算法,在同的业务场景也需要使用不同的算法(二)

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…