当前位置: 首页 > news >正文

数据结构之排序(5)

摘要:本文主要讲各种排序算法,注意它们的时间复杂度

概念

将各元素按关键字递增或递减排序顺序重新排列

评价指标

稳定性: 关键字相同的元素经过排序后相对顺序是否会改变

时间复杂度、空间复杂度

分类

内部排序——数据都在内存中

外部排序——数据太多,无法全部放入内存

一、插入排序

算法思想:每次将一个待排序的记录按其关键字大小插入到前面已排好序的子序列中,直到全部记录插入完成

比27大的数往后挪

void InsertSort() {int i, j ,temp;for (i = 1;i <= n;i++)//将各元素插入已排好序的序列中{if (A[i] < A[i - 1]){//若A[i]关键字小于前驱temp = A[i];//用temp暂存A[i]for (j = i - 1;j >= 0 && A[j] > temp;--j)//检查所有前面已排好序的元素{A[j + 1] = A[j];//所有大于temp的元素都向后挪位}A[j + 1] = temp;//复制到插入位置}}
}
void InsertSort() {int i, j;for (i = 2;i <= n;i++)//依次将A[2]~A[n]插入到前面已排序序列{if (A[i] < A[i - 1]) {//若A[i]关键码小于其前驱,将A[i]插入有序表A[0] = A[i];//复制为哨兵,A[0]不存放元素for (j = i - 1;A[0] < A[j];--j)//从后往前查找待插入位置{A[j + 1] = A[j];//向后挪位}}A[j + 1] = A[0];//复制到插入位置}}
}

优点:不用每轮循环判断j>=0

空间复杂度:O(1)

时间复杂度:主要来自对比关键字、移动元素 若n个元素,则需要n-1趟处理

最好时间复杂度(全部有序):O(n)

最坏(逆序):O(n^2)

平均O(n^2)

算法稳定性:稳定

优化——折半插入排序

当low>high时折半查找停止,应将[low,i-1]内的元素全部右移,并将A[0]复制到low所指位置

当A[mid]==A[0]时,为了保证算法的“稳定性”,应继续在mid 所指位置右边寻找插入位置

void InsertSort() {int i,j,low, high, mid;for (i = 2;i <= n;i++) {//依次将A[2]~A[n]插入前面的已排序序列A[0] = A[i];//将A[i]暂存到A[0]low = 1;high = i - 1;//设置折半查找的范围while (low <= high) {//折半查找mid = (low + high) / 2;//取中间点if (A[mid] > A[0])high = mid - 1;//查找左半子表else low = mid + 1;//查找右半子表}for (j = i - 1;j >= high + 1;--j)A[j + 1] = A[j];//统一后移元素,空出插入位置A[high + 1] = A[0];//插入操作}
}

二、希尔排序(Shell sort)

最好情况:原本就有序

比较好的情况:基本有序

希尔排序:先追求表中元素部分有序,再逐渐逼近全局有序

先将待排序表分割成若干形如L[i,i+d,i+2d,…,i+kd]的“特殊”子表,对各个子表分别进行直接插入排序。缩小增量d,重复上述过程,直到d=1为止

重点:给出增量序列,分析每一趟排序后的状态

void ShellSort() {int d, i, j;//A[0]只是暂存单元,不是哨兵,当j<=0时,插入位置已到for (d = n / 2;d >= 1;d = d / 2)//步长变化for (i = d ;i <n;++i)if (A[i] < A[i - d]) {//需将A[i]插入有序增量子表A[0] = A[i];//暂存在A[0]for (j = i - d;j > 0 && A[0] < A[j];j -= d)A[j + d] = A[j];//记录后移,查找插入的位置A[j + d] = A[0];//插入}
}

三、交换排序

1、冒泡排序

从后往前(或从前往后)两两比较相邻元素的值,若为逆序(即A[i-1]>A[i]),则交换它们,直到序列比较完。称这样过程为“一趟”冒泡排序。

void BubbleSort() {for (int i = 0;i < n - 1;i++) {bool flag = false;//表示本趟冒泡是否发生交换的标志for (int j=n;j > i;j--)//一趟冒泡过程if (A[j - 1] > A[j]) {//若为逆序swap(A[j - 1], A[j]);//交换flag = true;}if (flag == false)return;//本趟遍历后没有发生交换,说明表已经有序}
}

2、快速排序

算法思想:

在待排序表L[1,…n]中任取元素pivot作为枢轴(或基准,通常取首元素),

通过一趟排序将待排序表划分成独立的两部分L[1…k-1]和L[k+1…n], 使得L[1…k-1]中的所有元素小于pivot,L[k+1…n]大于等于,则pivot放在了其最终位置L(k)上,这个过程称为一次“划分”。

然后分别递归地对两个子表重复上述过程,直到每部分内只有一个元素或空为止,即所有元素放在了最终位置上

//用第一个元素将待排序序列划分为左右两个部分
int Partition( int low, int high) {int pivot = A[low];//第一个元素作为枢轴while (low < high) {//用low、high搜索枢轴的最终位置while (low < high && A[high] >= pivot) --high;A[low] = A[high];//比枢轴小的元素移动到左端while (low < high && A[low] <= pivot) ++low;A[high] = A[low];//大 右}A[low] = pivot;//枢轴元素存放到最终位置return low;//返回存放枢轴的最终位置
}//快速排序
void QuickSort(int low, int high) {if (low < high) {//递归跳出的条件int pivotpos = Partition( low, high);//划分QuickSort( low, pivotpos - 1);//划分左子表QuickSort(pivotpos + 1, high);//划分右子表}
} 

算法表现主要取决于递归深度,若每次“划分”越均匀,则递归深度越低。“划分”越不均匀,递归深度越深

三、选择排序

每一趟在待排序元素中选取关键字最小(或最大)的元素加入有序子序列

1、简单选择排序

void SelectSort() {for (int i = 0;i < n - 1;i++) {//一共进行n-1趟int min = i;//记录最小元素位置for (int j = i + 1;j < n;j++)//在A[i…n-1]中选择最小元素if (A[j] < A[min]) min = j;//更新最小元素位置if (min != i) swap(A[i], A[min]);//封装的swap()函数共移动元素3次}
}

2、堆排序

若满足L(i)>=L(2i)且L(i)>=L(2i+1)(1<=i<=n/2)——大根堆(大顶堆)

思路:

把所有非终端结点都检查一遍,是否满足大根堆的要求,如果不满足,则进行调整

顺序存储的完全二叉树中,非终端结点编号i<=[n/2]

检查当前结点是否满足根>=左、右

若不满足,将当前结点与更大的一个孩子互换

i的左孩子2i, 右孩子2i+1,父结点[i/2]

若元素互换破坏了下一级的堆,则采用相同的方法继续往下调整(小元素不断“下坠”)

(这个比较复杂,附视频:8.4_2_堆排序_哔哩哔哩_bilibili)

void BuildMaxHeap(int len) {for (int i = len / 2;i > 0;i--)//从后往前调整所有非终端结点,即非叶子结点HeadAdjust(i,len);
}
//将以k为根的子树调整为大根堆
void HeadAdjust(int k,int len) {A[0] = A[k];//A[0]暂存子树的根节点for (int i = 2*k;i <= len;i*=2) {//从左子树开始,沿key较大的子结点向下筛选if (i < len && A[i] < A[i + 1])//i < len 保证有右兄弟i++;//取key较大的子结点的下标if (A[0] >= A[i]) break;//筛选结束else {A[k] = A[i];//将A[i]调整到双亲结点上k = i;//修改k值,以便继续向下筛选}}A[k] = A[0];//被筛选结点的值放入最终位置
}

结论:一个结点,每“下坠”一层,最多只需对比关键字2次

若树高为h,某结点在第i层,则将这个结点向下调整最多只需要”下坠“h-i层,关键字对比次数不超过 2(h-i)

基于大根堆进行排序

//堆排序的完整逻辑
void HeapSort(int len) {BuildMaxHeap(len);//初始建堆for (int i = len;i > 1;i--) {//n-1趟的交换和建堆过程swap(A[i], A[1]);//堆顶元素和堆底元素互换HeadAdjust(1, i - 1);//把剩余的待排序元素整理成堆}
}

堆排序:每一趟将堆顶元素加入有序子序列(与待排序序列中的最后一个元素交换)

并将待排序序列中再次调整为大根堆(小元素不断:下坠)

得到“递增序列”

时间复杂度:O(n)+O(nlog2n)=O(nlog2n)

(O(n)建堆,O(nlog2n)排序)

稳定性:不稳定

堆的插入删除

插入:对于小根堆,新元素放到表尾,与父结点对比,若新元素比父结点更小,则将二者互换。新元素就这样一路“上升”,直到无法继续上升为止。

删除:被删除的元素用堆底元素替代,然后让该元素不断“下坠“,直到无法下坠为止

关键字对比次数

每次“上升”调整只需对比关键字1次

每次“下坠”调整可能需要对比关键字2次,也可能只需对比1次

四、基数排序

要求:得到按关键字“递减”的有序序列

基数排序不是基于“比较”的排序算法

第一趟:以“个位”进行“分配”

第一趟“收集”结束:得到按“个位”递减排序的序列

算法思想

将整个关键字拆分为d位(组)

按照各个关键字位权重递增的次序(如:个、十、百),做d趟“分配”和“收集”,若当前处理的关键字位可能取得r个值,则需要建立r个队列

分配:顺序扫描各个元素,根据当前处理的关键字位,将元素插入相应队列。一趟分配耗时O(n)

收集:把各个队列中的结点依次出队并链接。一趟收集耗时O(r)

性能

空间复杂度 O(r)

时间复杂度 O(d(n+r)) (一趟分配O(n),一趟收集O(r))

稳定

擅长处理

1、数据元素的关键字可用方便地拆分为d组,且d较小

2、每组关键字的取值范围不大,即r较小

3、数据元素个数n较大

五、归并排序

归并:把两个或多个已经有序的序列合并成一个

2路合并  k路合并

空间复杂度O(n)

时间复杂度O(nlogn)

稳定

//B是辅助数组
const int SIZE = sizeof(A) / sizeof(A[0]);
int B[SIZE];  void Merge(int A[], int low, int mid, int high) {for (int k = low; k <= high; k++)B[k] = A[k]; int i = low, j = mid + 1, k = i;while (i <= mid && j <= high) {if (B[i] <= B[j])A[k++] = B[i++];  elseA[k++] = B[j++];}while (i <= mid) A[k++] = B[i++];while (j <= high) A[k++] = B[j++];
}void MergeSort(int A[], int low, int high) {if (low < high) {int mid = (low + high) / 2;   MergeSort(A, low, mid);      MergeSort(A, mid + 1, high); Merge(A, low, mid, high);    }
}
http://www.lryc.cn/news/454390.html

相关文章:

  • R包的安装、加载以及如何查看帮助文档
  • 【YOLO学习】YOLOv3详解
  • JDK1.0主要特性
  • CSS基础-盒子模型(三)
  • 深度学习中的损失函数详解
  • 系统架构设计师-下午案例题(2022年下半年)
  • 高级图片编辑器Photopea
  • 详解zookeeper四字命令
  • docker 进入容器运行命令
  • 一行 Python 代码能实现什么丧心病狂的功能?圣诞树源代码
  • mit6824-01-MapReduce详解
  • 在Docker中运行微服务注册中心Eureka
  • 白话进程>线程>协程
  • 论文阅读:Attention is All you Need
  • 【Linux 】文件描述符fd、重定向、缓冲区(超详解)
  • Unity WebGL使用nginx作反向代理处理跨域,一些跨域的错误处理(添加了反向代理的配置依旧不能跨域)
  • 视频转文字免费的软件有哪些?6款工具一键把视频转成文字!又快又方便!
  • 解决DHCP服务异常导致设备无法获取IP地址的方法
  • Python机器学习模型的部署与维护:版本管理、监控与更新策略
  • 免费送源码:Java+ssm+JSP+Ajax+MySQL SSM汽车租赁管理系统 计算机毕业设计原创定制
  • Vivado viterbi decoder license
  • 【FastAdmin】PHP的Trait机制:代码复用的新选择
  • 小红书制作视频如何去原视频音乐,视频如何去原声保留背景音乐?
  • netty之Netty使用Protobuf传输数据
  • 【力扣 | SQL题 | 每日四题】力扣2082, 2084, 2072, 2112, 180
  • 快速了解Java中的15把锁!
  • TypeScript 封装 Axios 1.7.7
  • 【数据结构】【链表代码】移除链表元素
  • 作文-杭州游记
  • 降压芯片TPS54821