最近想复习下C++,很久没怎么用了,毕业时的一些经典排序算法也忘差不多了,所以刚好一起再学习一遍。

除了冒泡、插入、选择这几个复杂度O(n^2)的基本排序算法,希尔、归并、快速、堆排序,多多少少还有些晦涩难懂,幸好又博客园大神dreamcatcher-cx都总结成了图解,一步步很详细,十分感谢。

而且就时间复杂度来说,这几种算法到底有什么区别呢,刚好做了下测试。

代码参考: http://yansu.org/2015/09/07/sort-algorithms.html

//: basic_sort

#include <iostream>
#include <vector>
#include <ctime>
#include <string> using namespace std; // 获取函数名字的宏
#define GET_NAME(x) #x
// 生成随机数的宏
#define random(a,b) (rand()%(b-a+1)+a)
// 打印容器对象(vector)的宏
#define PRT(nums) { \
for(int i =; i<nums.size(); i++){ \
cout << nums[i] << " "; \
}\
} /*
冒泡排序
基本思想: 对相邻的元素进行两两比较,顺序相反则进行交换,这样,每一趟会将最小或最大的元素“浮”到顶端,最终达到完全有序
图解: http://www.cnblogs.com/chengxiao/p/6103002.html
考的最多的排序了吧。
1. 两层循环,最里面判断两个数的大小,大的换到后面(正序)
2. 内部循环一遍后,最大的数已经到最后面了
3. 下一次内部循环从0到倒数第二个数(最后一个数通过第一步循环比较已经最大了)
4. 依次循环下去
时间复杂度O(n^2),空间复杂度是O(n)
*/
void bubble_sort(vector<int> &nums)
{
for (int i = ; i < nums.size() - ; i++) { // i用来控制已经冒泡的数字个数
for (int j = ; j < nums.size() - i - ; j++) { // 从最左边遍历到最后一个没有浮动的数字
if (nums[j] > nums[j + ]) {
nums[j] += nums[j + ];
nums[j + ] = nums[j] - nums[j + ];
nums[j] -= nums[j + ];
}
}
}
} /*
插入排序
基本思想: 每一步将一个待排序的记录,插入到前面已经排好序的有序序列中去,直到插完所有元素为止。
图解: http://www.cnblogs.com/chengxiao/p/6103002.html
1. 两层循环,第一层i表示从左开始已经排好虚的部分
2. 第二层循环,将当前的数以及它前面的所有数两两比较,交换大的数到后面(正序)
3. 保证前面的数是排序好的,将新读取的数通过遍历前面排好序的部分并比较,插入到合适的位置
时间复杂度O(n^2),空间复杂度是O(n)
*/
void insert_sort(vector<int> &nums)
{
for (int i = ; i < nums.size(); i++) { // i表示从左开始已经排好序的部分
for (int j = i; j > ; j--) { // 从当前数字位置遍历到最左边的数字位置
if (nums[j] < nums[j - ]) {
int temp = nums[j];
nums[j] = nums[j - ];
nums[j - ] = temp;
}
}
}
} /*
选择排序
图解: http://www.cnblogs.com/chengxiao/p/6103002.html
基本思想: 每一趟从待排序的数据元素中选择最小(或最大)的一个元素作为首元素
1. 两层循环,第一层从左到右遍历,读取当前的数
2. min存放最小元素,初始化为当前数字
3. 内部循环遍历和比较当前数字后后面所有数字的大小,如果有更小的,替换min为更小数字的位置
4. 内部遍历之后检查min是否变化,如果变化,说明最小的数字不在之前初始化的min位置,交换使每次循环最小的元素被移动到最左边。
时间复杂度O(n^2),空间复杂度是O(n)
*/
void selection_sort(vector<int> &nums)
{
for (int i = ; i < nums.size(); i++) { // 从左到右遍历所有数字
int min = i; // 每一趟循环比较时,min用于存放较小元素的数组下标,这样当前批次比较完毕最终存放的就是此趟内最小的元素的下标,避免每次遇到较小元素都要进行交换。
for (int j = i + ; j < nums.size(); j++) {
if (nums[j] < nums[min]) {
min = j;
}
}
if (min != i) { //进行交换,如果min发生变化,则进行交换
int temp = nums[i];
nums[i] = nums[min];
nums[min] = temp;
}
}
} /*
希尔排序
图解: http://www.cnblogs.com/chengxiao/p/6104371.html
基本思想: 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
1. 最外层循环设置间隔(gap),按常规取gap=length/2,并以gap = gap/2的方式缩小增量
2. 第二个循环从gap位置向后遍历,读取当前元素
3. 第三个循环从当前元素所在分组的上一个元素开始(即减去gap的位置),通过递减gap向前遍历分组内的元素,其实就是比较分组内i和i-gap元素的大小,交换大的到后面
希尔排序的时间复杂度受步长的影响,不稳定。
*/
void shell_sort(vector<int> &nums)
{
for (int gap = int(nums.size()) >> ; gap > ; gap >>= ) { // 遍历gap
for (int i = gap; i < nums.size(); i++) { // 从第gap个元素向后遍历,逐个对其所在组进行直接插入排序操作
int j = i - gap; // j是这个分组内i元素的上一个元素
for (; j >= && nums[j] > nums[i]; j -= gap) { // 从i向前遍历这个分组内所有元素,把大的交换到后面
swap(nums[j + gap], nums[j]);
}
}
}
} // 合并两个有序序列
void merge_array(vector<int> &nums, int b, int m, int e, vector<int> &temp)
{
// cout << "b: " << b << " " << "m: " << m << " " << "e: " << e << endl;
int lb = b, rb = m, tb = b;
while (lb != m && rb != e)
if (nums[lb] < nums[rb])
temp[tb++] = nums[lb++];
else
temp[tb++] = nums[rb++]; while (lb < m)
temp[tb++] = nums[lb++]; while (rb < e)
temp[tb++] = nums[rb++]; for (int i = b;i < e; i++)
nums[i] = temp[i];
// cout << "temp: ";
// PRT(temp);
// cout << endl;
} //递归对序列拆分,从b(开始)到e(结束)的序列,取中间点(b + e) / 2拆分
void merge_sort_recur(vector<int> &nums, int b, int e, vector<int> &temp)
{
int m = (b + e) / ; // 取中间位置m
if (m != b) {
merge_sort_recur(nums, b, m, temp);
merge_sort_recur(nums, m, e, temp);
merge_array(nums, b, m, e, temp); // 开始(b)到中间(m) 和 中间(m)到结束(e) 两个序列传给合并函数
}
} /*
归并排序
图解: http://www.cnblogs.com/chengxiao/p/6194356.html
基本思想: 利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
1. 合并两个有序序列的函数,合并后结果存入临时的temp
2. 从中间分,一直递归分到最小序列,即每个序列只有一个元素,单位为1(一个元素肯定是有序的)
3. 然后两两比较合并成单位为2的n/2个子数组,在结果上继续两两合并
时间复杂度是O(nlogn),空间复杂度是O(n)。
*/
void merge_sort(vector<int> &nums){
vector<int> temp;
temp.insert(temp.begin(), nums.size(), ); // 定义和初始化temp用于保存合并的中间序列
merge_sort_recur(nums, , int(nums.size()), temp);
} // 将启始位置b作为基准,大于基准的数移动到右边,小于基准的数移动到左边
void quick_sort_recur(vector<int> &nums, int b, int e)
{
if (b < e - ) {
int lb = b, rb = e - ;
while (lb < rb) { // 遍历一遍,把大于基准的数移动到右边,小于基准的数移动到左边
while (nums[rb] >= nums[b] && lb < rb) //默认第一个数nums[b]作为基准
rb--;
while (nums[lb] <= nums[b] && lb < rb)
lb++;
swap(nums[lb], nums[rb]);
}
swap(nums[b], nums[lb]);
// cout << "nums: ";
// PRT(nums);
// cout << endl;
quick_sort_recur(nums, b, lb);
quick_sort_recur(nums, lb + , e);
}
} /*
快速排序
图解: http://www.cnblogs.com/chengxiao/p/6262208.html
基本思想: 快速排序也是利用分治法实现的一个排序算法。快速排序和归并排序不同,它不是一半一半的分子数组,而是选择一个基准数,把比这个数小的挪到左边,把比这个数大的移到右边。然后不断对左右两部分也执行相同步骤,直到整个数组有序。
1. 用一个基准数将数组分成两个子数组,取第一个数为基准
2. 将大于基准数的移到右边,小于的移到左边
3. 递归的对子数组重复执行1,2,直到整个数组有序
空间复杂度是O(n),时间复杂度不稳定。
*/
void quick_sort(vector<int> &nums){
quick_sort_recur(nums, , int(nums.size()));
} // 调整单个二叉树的根节点和左右子树的位置,构建大顶堆
// 在左右子树中挑出最大的和根节点比较,把最大的数放在根节点即可
void max_heapify(vector<int> &nums, int root, int end)
{
int curr = root; // 根结点
int child = curr * + ; // 左子树
while (child < end) {
if (child + < end && nums[child] < nums[child + ]) {
child++;
}
if (nums[curr] < nums[child]) {
int temp = nums[curr];
nums[curr] = nums[child];
nums[child] = temp;
curr = child;
child = * curr + ;
} else {
break;
}
}
} /*
堆排序
图解: http://www.cnblogs.com/chengxiao/p/6262208.html
基本思想: 将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了
堆的概念(i是一个二叉树的根节点位置,2i+1和2i+2分别是左右子树):
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
1. 由底(最后一个有叶子的根节点n/2-1)自上构建大顶堆
2. 根节点(0)和末尾交换,末尾变为最大
3. 对余下的0到n-1个数的根节点(0)二叉树进行大顶堆调整(调用max_heapify)(根节点(0)的叶子节点已经大于下面的所有数字了)
堆执行一次调整需要O(logn)的时间,在排序过程中需要遍历所有元素执行堆调整,所以最终时间复杂度是O(nlogn)。空间复杂度是O(n)。
*/
void heap_sort(vector<int> &nums)
{
int n = int(nums.size());
for (int i = n / - ; i >= ; i--) { // 构建大顶堆
max_heapify(nums, i, n);
} for (int i = n - ; i > ; i--) { // 排序, 将第一个节点和最后一个节点交换,确保最后一个节点最大
int temp = nums[i];
nums[i] = nums[];
nums[] = temp;
max_heapify(nums, , i); // 重新调整最顶部的根节点
}
} void func_excute(void(* func)(vector<int> &), vector<int> nums, string func_name){
clock_t start, finish;
start=clock();
(*func)(nums);
finish=clock();
// PRT(nums); // 打印每次的排序结果
cout << endl;
cout << func_name << "耗时:" << float(finish-start)/float(CLOCKS_PER_SEC)* << " (ms) "<< endl;
} int main() {
vector<int> b;
srand((unsigned)time(NULL));
for(int i=;i<;i++)
b.insert(b.end(), random(,));
cout << "数组长度: " << b.size() << "; ";
// PRT(b); // 打印随机数组
cout << endl; void (*pFun)(vector<int> &);
string func_name; pFun = bubble_sort;
func_name = GET_NAME(bubble_sort);
func_excute(pFun, b, func_name); pFun = insert_sort;
func_name = GET_NAME(insert_sort);
func_excute(pFun, b, func_name); pFun = selection_sort;
func_name = GET_NAME(selection_sort);
func_excute(pFun, b, func_name); pFun = shell_sort;
func_name = GET_NAME(shell_sort);
func_excute(pFun, b, func_name); pFun = merge_sort;
func_name = GET_NAME(merge_sort);
func_excute(pFun, b, func_name); pFun = quick_sort;
func_name = GET_NAME(quick_sort);
func_excute(pFun, b, func_name); pFun = heap_sort;
func_name = GET_NAME(heap_sort);
func_excute(pFun, b, func_name);
} ///:~

在数组很小的情况下,没有太大区别。但是较长数组,考的最多的冒泡排序就明显比较吃力了~

具体原因只能从时间复杂度上面来看,但为什么差这么多,我也不是完全明白~

运行结果,排序算法分别耗时:

数组长度: 5000; 

bubble_sort耗时:183.4 (ms) 

insert_sort耗时:106.525 (ms) 

selection_sort耗时:68.036 (ms) 

shell_sort耗时:1.096 (ms) 

merge_sort耗时:1.226 (ms) 

quick_sort耗时:1.398 (ms) 

heap_sort耗时:1.514 (ms)
Program ended with exit code: 0

七种常见经典排序算法总结(C++)的更多相关文章

  1. 七种常见经典排序算法总结(C++实现)

    排序算法是非常常见也非常基础的算法,以至于大部分情况下它们都被集成到了语言的辅助库中.排序算法虽然已经可以很方便的使用,但是理解排序算法可以帮助我们找到解题的方向. 1. 冒泡排序 (Bubble S ...

  2. python3实现几种常见的排序算法

    python3实现几种常见的排序算法 冒泡排序 冒泡排序是一种简单的排序算法.它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来.走访数列的工作是重复地进行直到没有再需要 ...

  3. java讲讲几种常见的排序算法(二)

    java讲讲几种常见的排序算法(二) 目录 java讲讲几种常见的排序算法(一) java讲讲几种常见的排序算法(二) 堆排序 思路:构建一个小顶堆,小顶堆就是棵二叉树,他的左右孩子均大于他的根节点( ...

  4. java讲讲几种常见的排序算法

    java讲讲几种常见的排序算法(一) 目录 java讲讲几种常见的排序算法(一) java讲讲几种常见的排序算法(二) 以数组array={6,3,20,8,15,1}为例 冒泡排序 思路:从第0个到 ...

  5. java几种常见的排序算法总结

    /*************几种常见的排序算法总结***************************/ package paixu; public class PaiXu { final int  ...

  6. Python全栈开发之5、几种常见的排序算法以及collections模块提供的数据结构

    转载请注明出处http://www.cnblogs.com/Wxtrkbc/p/5492298.html 在面试中,经常会遇到一些考排序算法的题,在这里,我就简单了列举了几种最常见的排序算法供大家学习 ...

  7. 用php实现四种常见的排序算法

    几种常见的排序 排序是一个程序员的基本功,对于初级phper,更是可以通过排序算法来锻炼自己的思维能力. 所谓排序,就是对一组数据,按照某个顺序排列的过程.下面就总结四种常用的php排序算法,分别是冒 ...

  8. Java几种常见的排序算法

    一.所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作.排序算法,就是如何使得记录按照要求排列的方法.排序算法在很多领域得到相当地重视,尤其是在大量数据的处理方面. ...

  9. Java实现7种常见的排序算法

    数据结构中的内部排序:不需要访问外存便能完成,是一个逐步扩大记录的有序序列长度的过程. 可以分为5类: 1.插入排序:直接插入排序,稳定排序,时间复杂度为O(n^2)非递减有序,设置r[0]为哨兵进行 ...

随机推荐

  1. 查看电脑已经连过的wifi密码

    用下面两条命令可以完成 查看当前系统已经保存的网络 netsh wlan show profiles 查看wifi指定密码 netsh wlan show profiles name="wi ...

  2. Web安全学习笔记之Nmap脚本使用指南

    nmap是一个网络连接端扫描软件,用来扫描网上电脑开放的网络连接端.确定哪些服务运行在哪些连接端,并且推断计算机运行哪个操作系统.它是网络管理员必用的软件之一,以及用以评估网络系统安全. —— 来自百 ...

  3. 20145302张薇 《Java程序设计》第二周学习总结

    20145302张薇 <Java程序设计>第一周学习总结 教材学习内容总结 第三章 第三章讲的是基本类型,变量,运算符和基本条件语句. 基本类型分为: 整数:short(2 byte),i ...

  4. hdu5727

    Necklace SJX has 2*N magic gems. N of them have Yin energy inside while others have Yang energy. SJX ...

  5. Servlet3.0与Spring

    servlet   filter  listener  web.xml DispatcherServlet  web.xml 后期 servlet3.0我们去除web.xml 通过注解方式 同时需要s ...

  6. Pandas 高级应用 数据分析

    深入pandas 数据处理 三个阶段 数据准备 数据转化 数据聚合 数据准备 加载 组装 合并 - pandas.merge() 拼接 - pandas.concat() 组合 - pandas.Da ...

  7. TCP状态切换流程

    enum { /* * Description of States: * * TCP_SYN_SENT sent a connection request, waiting for ack * * T ...

  8. centos添加php及mysql环境变量

    在Linux CentOS系统上安装完php和MySQL后,为了使用方便,需要将php和mysql命令加到系统命令中,如果在没有添加到环境变量之前,执行 “php -v”命令查看当前php版本信息时时 ...

  9. authentication vs authorization 验证与授权的区别

    认证和授权的区别 Authentication vs. Authorization简单来说,认证(Authentication )是用来回答以下问题: 用户是谁 当前用户是否真的是他所代表的角色 通常 ...

  10. [spring]Bean注入——在XML中配置

    Bean注入的方式有两种: 一.在XML中配置 属性注入 构造函数注入 工厂方法注入 二.使用注解的方式注入@Autowired,@Resource,@Required 本文首先讲解在XML中配置的注 ...