代码实践几种常见算法

好久不看基础算法了。。。今天花点时间重新温习一下。
主要实现了下面几个排序算法:

  1. 插入排序
  2. 选择排序
  3. 归并排序
  4. 冒泡排序
  5. 堆排序
  6. 快速排序

事实证明,一段时间不用,记忆就会出现偏差。今天写了一下,感觉对这几个算法又多了一些理解~

程序实现

//
//  main.cpp
//  sort_algorithm_learning
//
//  Created by cslzy on 16/8/24.
//  Copyright © 2016年 CY. All rights reserved.
//

#include <iostream>
#include <vector>

using namespace std;

void swapa(int *a, int *b);
void printa(int a[], int al);
void insert_sort(int a[], int al);// 插入排序是每次形成一个相对有序的子集
void select_sort(int a[], int al);
void merge_sort(int a[], int left, int right);
void bubble_sort(int a[], int al);// 与插入排序相比,冒泡排序每次找到一个绝对位置的数据(这个数值的最终位置)
void heap_sort(int a[], int al);
void max_heapify(int a[], int root, int end);

void quick_sort(int a[], int left, int right);
int quick_one_shot(int a[], int left, int right);// return the pivot


int main(int argc, const char * argv[]) {
    // insert code here...
    std::cout << "Hello, World!\n";
    // 使用 python 生成一个随机数组 :np.random.randint(10, size=[1,100])
    int a1[] = {21, 37, 93,  8, 66, 58, 76, 91, 42, 84};

    int a2[] = {49, 67, 77, 81, 23, 13, 28, 80,  5, 17, 90, 67, 45,  5, 37, 63, 25, 8,  4, 35, 50, 13, 42,  5, 83, 85, 74, 31, 11, 84, 46, 56, 87, 42,
        71, 79, 59, 75,  4, 10, 23, 44,  6, 15, 95, 90, 75, 14, 44, 81, 36,
        75, 30, 29, 62, 63, 98, 78, 51, 66, 56, 75, 77, 50, 41, 77, 44, 38,
        19,  7, 29,  9, 52, 88, 92, 89, 80, 82, 67, 63, 92, 62, 44, 81, 17,
        69, 17, 96, 78, 86, 67, 95, 73, 59, 62, 74, 58, 83, 34, 78};

    vector<int> arr;
    for(auto v: a1) // 在这里改变想测试的数组名称
        arr.push_back(v);

    int al = (int)arr.size();
    int b[al];
    for(int i = 0; i < al; i++) b[i] = arr[i];

    int s = 5;
    string algorithm[] = {"insert sort", "select sort", "merge sort", "bubble sort"
        ,"heap sort"
        ,"quick sort"};
    cout<<"before sort"<<endl;
    printa(b, al);
    switch(s)
    {
        case 0:// insert sort
        {
            //int al = sizeof(a) / sizeof(*a);
            insert_sort(b, al);
            break;
         }
        case 1: // select sort
        {
            select_sort(b, al);
            break;
        }
        case 2: // merge sort
        {
            cout<<"merge sort"<<endl;
            merge_sort(b, 0, al - 1);
            break;
        }
        case 3: // bubble sort
        {
            cout<<algorithm[3]<<endl;
            bubble_sort(b, al);
            break;
        }
        case 4:
        {
            cout<<algorithm[4]<<endl;
            heap_sort(b, al);
            break;
        }
        case 5:
        {
            cout<<algorithm[5]<<endl;
            quick_sort(b, 0, al - 1);
            break;
        }
        default:
            cout<<"no optional choice"<<endl;
            break;
    }
    cout<<"result after "<<algorithm[s]<<" sort:"<<endl;
    for(auto i: b)
        cout<<i<<" ";
    cout<<endl;
    return 0;
}

void quick_sort(int a[], int left, int right)
{
    if(left >= right) return;
    int p = quick_one_shot(a, left, right);
//    cout<<"separate by "<<a[p]<<endl;
//    for(int i = left; i < right; i++)
//        cout<<a[i]<<" ";
//    cout<<endl;

    quick_sort(a, left, p - 1);
    quick_sort(a, p + 1, right);
    // 快速排序
    // 平均和最好情况下的时间复杂度是O(nlogn),最优是在每次选择的轴点能比较均匀地分开数组
    // 最坏是在升序或降序的时候,为O(n^2)
    // 不稳定
    // 空间复杂度,最好是O(nlogn),最坏是O(n^2)
    // n较大时,比较好。

}

int quick_one_shot(int a[], int left, int right)
{
    int p = left;//选择首元素当做第一个轴点
    int temp = a[left];

    while(left < right)
    {
        while(left < right && a[right] > temp) right--; // 找到右为第一个比轴点大的元素
        // 交换
        if(left < right)
        {
            a[p] = a[right];
            p = right;
        }
        while(left < right && a[left] < temp) left++;
        if(left < right)
        {
            a[p] = a[left];
            p = left;
        }
    }
    a[p] = temp;
    return p;
}

void heap_sort(int a[], int al)
{
    // 建一个大根堆
    for(int i = (al - 1 - 1) / 2; i >= 0; i--)
        max_heapify(a, i, al - 1);

    cout<<"排序过程"<<endl;
    // 每次输出一个结点,并将剩下的结点排序
    for(int i = al - 1; i > 0; i--)
    {
        printa(a, al);
        swapa(&a[0], &a[i]);
        max_heapify(a, 0, i - 1);
    }

    // 堆排序 : 我原来印象中的堆排序一直都是错的。。。
    // 预处理,n/2个结点,最大深度是logn(2为底)
    // (n - 1)次调整,每次的最大深度是logn(2为底)
    // 时间复杂度均为O(nlogn)
    // 空间复杂度O(1)
    // 不稳定
    // 适用于n较大的情况

      // 我下面这个方法每次都把第一个非叶了结点到根结点的所有结点与其子结点比较一遍,
      // 找出一个最大值,显然这个方法并不是堆排序。
//    for(int i = al - 1; i > 0; i--)
//    {
//        for(int j = (i - 1) / 2; j >= 0; j--)
//        {
//            int son = j * 2 + 1; // left child, must exists.
//            if(a[j] < a[son]) swapa(&a[j], &a[son]);
//            
//            son++; // right child
//            if(son <= i && a[j] < a[son]) swapa(&a[j], &a[son]);
//        }
//        swapa(&a[0], &a[i]);
        printa(a, al);// 输出每次排序后的结果,用于检查。
//    }
    // 堆排序
    //
}

void max_heapify(int a[], int root, int end)
{
    int parent = root;
    int son = parent * 2 + 1; // first son
    while(son <= end)
    {
        if(son + 1 <= end && a[son] < a[son + 1]) son++; // 判断一下有没有右孩子
        if(a[parent] < a[son])
        {
            swapa(&a[parent], &a[son]);
            parent = son;
            son = parent * 2 + 1;
        }
        else
            return;
    }
}

void bubble_sort(int a[], int al)
{
    for(int i = al; i > 0; i--)
    {
        int flag = true;// 判断条件
        for(int j = 0; j < i - 1; j++)
        {
            if(a[j] > a[j + 1]) // 逐个比较,没有跳过的,所以是稳定算法。
            {
                flag = false;
                int temp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = temp;
            }
        }
        if(flag) i = 0; // 如果在一个子遍历过程中,未发现逆序,则终止。
    }
    // 冒泡排序
    // 可以是稳定的算法。
    // 最好时间复杂度O(n), 平均和最坏都是O(n^2)。
    // 空间复杂度O(1)
    // 适用于n较小。这里说明一下,初始输入相对有序应该也是有帮助的。
}

void merge_sort(int a[], int left, int right)
{
    if(left >= right) return;
    int mid = (left + right) / 2;
    int len = right - left + 1;
    int l = left;
    int r = mid + 1;

    // merge left and right subarray
    merge_sort(a, left, mid);
    merge_sort(a, mid + 1, right);

    int k = 0;
    int temp[len];
    while(k < len)
    {
        // 下面这两句没有考虑到一半读完了,而另外一半没有读完的情况。
        //while(l < mid + 1 && a[l] <= a[r]) temp[k++] = a[l++];
        //while(r < right + 1 && a[r] < a[l]) temp[k++] = a[r++];
        while(l < mid + 1 && (a[l] <= a[r] || r == right + 1)) temp[k++] = a[l++];
        while(r < right + 1 && (a[r] < a[l] || l == mid + 1)) temp[k++] = a[r++];
    }

    for(int i = 0; i < len; i++)
        a[left + i] = temp[i];
    // 归并排序
    // 可以是稳定的
    // 因为分层都要并n次,一共logn(2为底)次,所以时间复杂度是O(nlogn)。与初始序列无关。
    // n较大时比较好。。。因为小了,也体现不出来优势。
    // 空间复杂度O(n),因为每层归并都要一些临时数组,一层加起来就是n个
}
void insert_sort(int a[], int al)// 这样传过来的就是指针了。。。
{
    // int al = sizeof(a) / sizeof(*a); //这里的a是一个指针(8 byte),所以结果和上面的不同。
    for(int i = 1; i < al; i++)
    {
        int j = i;
        // 下面这个操作,是为了找到第一个不大于j位置元素的值。
        while(j && a[j] < a[j - 1])
        {
            int temp = a[j];
            a[j] = a[j - 1];
            a[j - 1] = temp;
            j--;
        }
    }
    // 插入排序
    // 它是一个稳定的排序法(不会交换相同的数值的位置)
    // 平均和最坏时间复杂度是O(n^2)
    // 最好情况的复杂度是O(n),若开始就有序(这里默认是增序),那么只需要线性扫描一遍数组。复杂度为O(n)
    // 适用于大部分已排好序的数组
    // 空间复杂度O(1),在交换数据时,需要一个temp变量。
}

void printa(int a[], int al)
{
    for(int i = 0; i < al; i++)
        cout<<a[i]<<" ";
    cout<<endl;
}

void swapa(int *a, int * b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

void select_sort(int a[], int al)
{
    // 选择排序的思想是一个完全的暴力法
//    cout<<"select sort"<<endl;
    for(int i = 0; i < al - 1; i++)
    {
        int min = i;
        // 从所有未排序的数值中找出最小值的位置
        for(int j = i + 1; j < al; j++)
            if(a[j] < a[min])
                min = j;
        // 交换(因为有交换操作的存在,使用这个算法变得不稳定,即相同的数值的相对位置在最终的排序结果里有可能发生变化)
        int temp = a[i];
        a[i] = a[min];
        a[min] = temp;
    }
    // 选择排序
    // 最好、最坏、平均的时间复杂度都是O(n^2)
    // 空间复杂度是O(1)
    // 不稳定
    // n较小时使用
}

参考

http://www.sobugou.com/blog/2015/09/02/chang-jian-sortingsuan-fa-zong-jie/
https://segmentfault.com/a/1190000002595152
冒泡和插入比较
演示排序的网站
快排时间复杂度

目录 目录 1 Graph 图论 3 | DAG 的深度优先搜索标记 3 | 无向图找桥 3 | 无向图连通度(割) 3 | 最大团问题 DP + DFS 3 | 欧拉路径 O(E) 3 | DIJKSTRA 数组实现 O(N^2) 3 | DIJKSTRA O(E * LOG E) 4 | BELLMANFORD 单源最短路 O(VE) 4 | SPFA(SHORTEST PATH FASTER ALGORITHM) 4 | 第 K 短路(DIJKSTRA) 5 | 第 K 短路(A*) 5 | PRIM 求 MST 6 | 次小生成树 O(V^2) 6 | 最小生成森林问题(K 颗树)O(MLOGM). 6 | 有向图最小树形图 6 | MINIMAL STEINER TREE 6 | TARJAN 强连通分量 7 | 弦图判断 7 | 弦图的 PERFECT ELIMINATION 点排列 7 | 稳定婚姻问题 O(N^2) 7 | 拓扑排序 8 | 无向图连通分支(DFS/BFS 邻接阵) 8 | 有向图强连通分支(DFS/BFS 邻接阵)O(N^2) 8 | 有向图最小点基(邻接阵)O(N^2) 9 | FLOYD 求最小环 9 | 2-SAT 问题 9 Network 网络流 11 | 二分图匹配(匈牙利算法 DFS 实现) 11 | 二分图匹配(匈牙利算法 BFS 实现) 11 | 二分图匹配(HOPCROFT-CARP 的算法) 11 | 二分图最佳匹配(KUHN MUNKRAS 算法 O(M*M*N)) 11 | 无向图最小割 O(N^3) 12 | 有上下界的最小(最大)流 12 | DINIC 最大流 O(V^2 * E) 12 | HLPP 最大流 O(V^3) 13 | 最小费用流 O(V * E * F) 13 | 最小费用流 O(V^2 * F) 14 | 最佳边割集 15 | 最佳点割集 15 | 最小边割集 15 | 最小点割集(点连通度) 16 | 最小路径覆盖 O(N^3) 16 | 最小点集覆盖 16 Structure 数据结构 17 | 求某天是星期几 17 | 左偏树 合并复杂度 O(LOG N) 17 | 树状数组 17 | 二维树状数组 17 | TRIE 树(K 叉) 17 | TRIE 树(左儿子又兄弟) 18 | 后缀数组 O(N * LOG N) 18 | 后缀数组 O(N) 18 | RMQ 离线算法 O(N*LOGN)+O(1) 19 | RMQ(RANGE MINIMUM/MAXIMUM QUERY)-ST 算法 (O(NLOGN + Q)) 19 | RMQ 离线算法 O(N*LOGN)+O(1)求解 LCA 19 | LCA 离线算法 O(E)+O(1) 20 | 带权值的并查集 20 | 快速排序 20 | 2 台机器工作调度 20 | 比较高效的大数 20 | 普通的大数运算 21 | 最长公共递增子序列 O(N^2) 22 | 0-1 分数规划 22 | 最长有序子序列(递增/递减/非递增/非递减) 22 | 最长公共子序列 23 | 最少找硬币问题(贪心策略-深搜实现) 23 | 棋盘分割 23 | 汉诺塔 23 | STL 中的 PRIORITY_QUEUE 24 | 堆栈 24 | 区间最大频率 24 | 取第 K 个元素 25 | 归并排序求逆序数 25 | 逆序数推排列数 25 | 二分查找 25 | 二分查找(大于等于 V 的第一个值) 25 | 所有数位相加 25 Number 数论 26 1 |递推求欧拉函数 PHI(I) 26 |单独求欧拉函数 PHI(X) 26 | GCD 最大公约数 26 | 快速 GCD 26 | 扩展 GCD 26 | 模线性方程 A * X = B (% N) 26 | 模线性方程组 26 | 筛素数 [1..N] 26 | 高效求小范围素数 [1..N] 26 | 随机素数测试(伪素数原理) 26 | 组合数学相关 26 | POLYA 计数 27 | 组合数 C(N, R) 27 | 最大 1 矩阵 27 | 约瑟夫环问题(数学方法) 27 | 约瑟夫环问题(数组模拟) 27 | 取石子游戏 1 27 | 集合划分问题 27 | 大数平方根(字符串数组表示) 28 | 大数取模的二进制方法 28 | 线性方程组 A[][]X[]=B[] 28 | 追赶法解周期性方程 28 | 阶乘最后非零位,复杂度 O(NLOGN) 29 递归方法求解排列组合问题 30 | 类循环排列 30 | 全排列 30 | 不重复排列 30 | 全组合 31 | 不重复组合 31 | 应用 31 模式串匹配问题总结 32 | 字符串 HASH 32 | KMP 匹配算法 O(M+N) 32 | KARP-RABIN 字符串匹配 32 | 基于 KARP-RABIN 的字符块匹配 32 | 函数名: STRSTR 32 | BM 算法的改进的算法 SUNDAY ALGORITHM 32 | 最短公共祖先(两个长字符串) 33 | 最短公共祖先(多个短字符串) 33 Geometry 计算几何 34 | GRAHAM 求凸包 O(N * LOGN) 34 | 判断线段相交 34 | 求多边形重心 34 | 三角形几个重要的点 34 | 平面最近点对 O(N * LOGN) 34 | LIUCTIC 的计算几何库 35 | 求平面上两点之间的距离 35 | (P1-P0)*(P2-P0)的叉积 35 | 确定两条线段是否相交 35 | 判断点 P 是否在线段 L 上 35 | 判断两个点是否相等 35 | 线段相交判断函数 35 | 判断点 Q 是否在多边形内 35 | 计算多边形的面积 35 | 解二次方程 AX^2+BX+C=0 36 | 计算直线的一般式 AX+BY+C=0 36 | 点到直线距离 36 | 直线与圆的交点,已知直线与圆相交 36 | 点是否在射线的正向 36 | 射线与圆的第一个交点 36 | 求点 P1 关于直线 LN 的对称点 P2 36 | 两直线夹角(弧度) 36 ACM/ICPC 竞赛之 STL 37 ACM/ICPC 竞赛之 STL 简介 37 ACM/ICPC 竞赛之 STL--PAIR 37 ACM/ICPC 竞赛之 STL--VECTOR 37 ACM/ICPC 竞赛之 STL--ITERATOR 简介 38 ACM/ICPC 竞赛之 STL--STRING 38 ACM/ICPC 竞赛之 STL--STACK/QUEUE 38 ACM/ICPC 竞赛之 STL--MAP 40 ACM/ICPC 竞赛之 STL--ALGORITHM 40 STL IN ACM 41 头文件 42 线段树 43 求矩形并的面积(线段树+离散化+扫描线) 43 求矩形并的周长(线段树+离散化+扫描线) 44
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值