Contents

PrefaceSortTechniques1. Heap Sort2. Merge Sort3. Quick Sort4. Count Sort5. Bin/Bucket Sort6. Radix Sort7. Bubble Sort8.Selection Sort9. Insertion Sort

Preface

Udemy上买的那个印度大哥的数据结构与算法的课程快看完了,先整理一些东西,还有一些比较难就先放着吧,以后再学了。

还有,这些方面我是入门的小白,本篇推应该会有地方表述的不好。有的地方内容太多了,不好展开说(字数太多了,应该还会有一些错别字没找出来)。最后的代码我会放到gitee上,如下:

https://gitee.com/mitchhong/SortTechniques.git

日常种应该不用自己写这些算法,C++的STL种有sort就能完成,或者对于list链表容器来说用它自己的sort方法。

此外我们还可以把数组中的元素拷贝到 priority_queue,或者multi_set 中也能完成排序的工作!

SortTechniques

这块老师讲了 11 种排序算法,我就学了9种。

时间复杂度为 O(n^2) 的有3种

  • Bubble Sort

  • Insertion Sort

  • Selection Sort

时间复杂度为 O(nlogn) 的有4种

  • Heap Sort

  • Merge Sort

  • Quick Sort

  • Tree Sort(我没看这个。。。)

时间复杂度为 O(n) 的有3种

  • Count Sort

  • Bucket/Bin Sort

  • Radix Sort

还有一个时间复杂度为 O(n^(3/2))

  • Shell Sort (这个我也没看)

时间复杂度的分析过程这里就不写了。

下面就介绍一下我看了的那 9 种排序算法,先介绍 O(nlogn)的那三个

1. Heap Sort

该算法一共四部分内容:

  • 在Max Heap中插入元素

  • 创建一个Max Heap

  • 从Max Heap中删除一个元素

  • Heap Sort


按照老师上课讲的意思是:

  • Heap 就是一个内存连续的数组;

  • 可以用一个数组组来表示(树有两种表示方法:Array Representation 和 Linked Representation);

  • 如果树要用数组来表示的话,那么这个树必须是一个Complete Binary Tree

  • 还需要这个Complete Binary Tree中的每一个父节点的值都要比子节点的值大!也就是一个Max Heap

关于Complete Binary Tree 如下笔记:

它是把数组中的元素从左到右一行一行地填入Binary Tree中的;

所以 Heap must be a complete Binary Tree

此外还有一个点需要了解,就是数组中的第 i 个元素映射为树中的节点后,其父节点和左右子节点在数组中的索引

  • element:i

  • left child:2*i

  • right child:2*i + 1

  • parent:i/2 (整除)

此处是把数组的第0位空出来,存数时是从第一位开始存的,所以下标就从1开始了;然你改成从0开始算的也行(i换成i-1)

下面还是放一下笔记的内容:

通过上面的说明,Max Heap对应的Complete Binary Tree,越靠上层其值越大!这就可以看出有点排序的意思了!

下面先写一下向Max Heap中插入元素的方法,这个是Heap Sort的一部分!

1.1 Inserting In a Heap

我们插入的元素都是放在数组的最后的,但是对于Max Heap来说,大的值应该放在上层,不应该放在底层。所以我们就需要把插入的值与它的父节点进行比较一下,如果插入的值大于其父节点则把他俩互换一下继续进行比较。

此外,这里插入并非是从外界拿一个值放到数组中,而是我们要插入的值原来就在数组中放好了;比如:

/** * @brief 向MaxHeap中插入元素 *      实际上所有的元素都是放在了vec中,只不过我们给MaxHeap界定了一个范围, *      数组的 前n-1个是堆中的元素 *      数组的 后 n~end 不在堆中 *      下标从1开始算,第0位的值忽略,数组也是从第一位开始存的 * */void Insert(vector<int>& vec, int n){    int temp = vec.at(n); /// 要插入的值,整一个临时变量保存起来 //    int i = n; /// 把数组中的第n个值插入到堆中 //    while (i > 1 && temp > vec.at(i/2))    {/// 如果插入的值大于它当前的父节点(i/2)上的值,就把父节点的值往下放        vec.at(i) = vec.at(i/2);        i = i / 2;  /// i 再指向上一个父节点    }    /// while退出时,说明当前 i指向的节点的值是小于其父节点的值的,所以我们就把之前保存的要插入的那个值放过来!    vec.at(i) = temp;}

1.2 Creating a Heap

给你一个无序的数组,利用这个数组来创建一个堆,实际上就是循环向堆中插入。只不过刚开始时,堆中的元素只有数组中的第一个值,后面每插入一个元素,堆中就多一个,数组中待插入堆中的就少一个!

/** * @brief 创建堆,循环插入 *      注意,传入的数组的第0个位置是空出来的,从下标 1 开始是真正的值 *      开始时,堆中放的是 索引为1 上的值,也就是把第一个真正的值放到堆中,然后 从第二个真正的值开始插入! * */    void CreateHeap(vector<int>& vec){        for (int i = 2; i             Insert(vec, i);}

1.3 Deleting from Heap

在 Heap 中,我们只能删除 root,也就是只能从上到下 一层一层地删除!
You can only delete highest priority element!

因为删除中间其他的  会造成表示堆的  数组的中间部分  产生空白,这样就不是Complete Binary Tree了!

最后一个不能是因为我们要用最后一个来替换刚刚被删除的root,就是拿堆内的最后一个先顶上去。然后原本顶部root的元素被放到了最后。

在堆内的最后一个元素替换root后,此时的Heap 并不是Max Heap,因为他把最小的放到最上面了!所以我们还要对这个Heap进行遍历,比较节点的左右子节点,把大的那个节点与根节点互换,这样,根节点的值(最小值,我们之前把堆内的最后一个放到root上去了)就又降到最后了。

Delete一次后堆的长度减1。

整个过程,我重新画了一遍,如下:

经过观察可以发现,实际上一次删除就把最大值放到了数组的最后。所以堆排序的原理就是不断地从堆中删除一个元素(根节点)

/** * @brief 从MaxHeap中删除一个元素 *      只能删除root,每删除一个元素Heap中就少一个,删掉的元素被放在数组的最后 *      root拿掉后,要对新的Heap进行遍历,把从尾部替换到root的值给交换到后面去 * @param[in] vec 数组 * @param[in] n  堆的长度,堆范围内的最后一个元素的索引 * * Note:这个数组的第 0 位上的值还是要空出来的,Heap从第1位开始存! * */void Delete(vector<int>& vec, int n){    /// 数组的第0位上的元素是空出来的,从第1位开始存;Heap只能删除root,这里用一个变量把删掉的先保存起来    int deleted_val = vec.at(1);

    /// 把最Heap内的最后一个元素拿过来顶替刚被删掉的root    vec.at(1) = vec.at(n);

    /// 现在再把被删掉的元素放到最后就行了!    vec.at(n) = deleted_val;

    /// 下面准备遍历Heap,让其重新成为Max Heap;就是比较当前根节点和其两个子节点,把大的放到根节点上,互换    int cur_root = 1;  /// 当前根节点:    int cur_child = 2 * cur_root;  /// 当前根节点下较大的子节点,初始化为左节点,在循环中比较左右!

    while (cur_child -1)    {        /// 如果右边大于左边,那么就走树的右边这条线,不过先跳到这条线上来,对当前子节点进行 +1 就过来了        /// 如果这不成立的话,当前节点还是指向着当前左节点        if (vec.at(cur_child+1) > vec.at(cur_child)) ++cur_child;

        /// 比较当前较大的子节点和当前的根节点的大小!如果当前子节点大于当前根节点,就把他俩互换        /// 否则直接退出就行        if (vec.at(cur_child) > vec.at(cur_root))        {            /// 交换            std::swap(vec.at(cur_child), vec.at(cur_root));

            /// 更新cur_root为当前子节点,并(cur_child)移动到当前子节点的下一层子节点            cur_root = cur_child;            cur_child = 2 * cur_root;  /// 下一层的左子节点        }        else break;  /// 当前子节点小于根节点,说明已经是Max Heap了,直接退出!    }}

1.4 Heap Sort

上面三个部分整明白了,这个 Heap Sort就很简单了!

Heap Sort一共两步:

  • Create Heap of n elements;

  • Delete n elements One by One!

所以,代码如下:

/** * @brief Heap Sort * */void HeapSort(vector<int>& vec){    /// 1. Create (Max)Heap;    CreateHeap(vec);

    /// 2. Delete all elements One-by-One!    for (int i = (int)vec.size()-1; i > 1; --i)  /// 数组的第0位上空出来的,从第一位开始才是Heap的内容        Delete(vec, i);  /// 每Delete一次,Heap的长度都为 减1,所以这里把 i 作为第二个参数传进去}

2. Merge Sort

2.1 Merging 2 Sorted Lists
(这个List不是指链表,就是表示一个序列)

首先介绍一些合并两个有序数组AB的思路!一定要是有序的!

  1. 创建一个额外的数组C用来存结果

  2. 定义三个变量:i, j, k;i,j,k开始时都分别指向A,B,C这三个数组的开头,也就是i=j=k=0

  3. 写一个循环,只要A和B这两个数组有一个走到头了,就结束循环;在循环中比较A[i]和B[j],

  • 如果A[i] < B[j],那么就把A[i]拷贝到C[k]中。然后 i+1, k+1, j原地不动!

  • 如果A[i] >= B[j],那么就把B[j]拷贝到C[k]中。然后 j+1, k+1, i原地不动!

  1. 在上面的循环结束后:

  • 从i开始再单独遍历A,把A中剩下的所有元素都拷贝到C中去;

  • 从j开始再单独遍历B,把B中剩下的所有元素都拷贝到C中去;

  • 不过,上面这两个循环顶多会有一个被执行,因为第3步的循环已经走完一个数组了,这里为了省去判断哪个先走完了才直接写两个循环的!

这就结束了。

2.2 Merge 2 Sorted Lists in a Single Array

这个其实和上面基本是一模一样啦,就是把上面说的 A和B 一前一后 两段 拼成一个数组。对这个数组的两段进行合并。

唯一的变动就是上面的i 和 j要变一下子,代码如下:

/** * @brief 这里还是用下标比迭代器方便操作 *      h 还是表示当前段最后一个元素 * @param[in] vec 整个数组(包括两个有序段) * @param[in] l   左边有序段的起始位置(不写0是为了后面的MergeSort准备的) * @param[in] mid 左边有序段的终止位置 * @param[in] h   右边有序段的起始位置  * */template <typename T>void Merging(std::vector& vec, int l, int mid, int h){    std::vector tmpVec(vec.size());int i = l;     /// [l, mid]    左边的从 l到midint j = mid+1; /// [mid+1, h]  右边的从 mid+1到lint k = l;     /// 中间数组tmpVec的下标,也是要从 l 开始/// 对这两段数组进行遍历while (i <= mid && j <= h)    {if (vec.at(i)             tmpVec.at(k++) = vec.at(i++);else            tmpVec.at(k++) = vec.at(j++);    }/// 最后一定有一个数组没有遍历完while (i <= mid) {tmpVec.at(k++) = vec.at(i++);}while (j <= h) {tmpVec.at(k++) = vec.at(j++);}/// 把当前段的复制回去(假装原地修改)for(i = l; i <= h; i++)        vec.at(i) = tmpVec.at(i);}

用了模板,之前写的,懒得删了!

2.3 Merging Multiple Lists

如上图,A B C D 4个数组我按照垂直方向书写的,我们可以两两合并、两两合并、直到最后合并成一个整体!(当然你也可以按顺序合并,合并完一个再合并下一个)

2.4 Merge Sort

首先要明确一个问题:当一个数组只有一个元素时,这个数组就是有序的!

哈哈,既然这样,我一个有着 n 个元素的数组,就相当于是由 n 个有序的小数组合并而来的!

既然这样地话,那我们就和上面一样地思路,不断地把一个数组一分为二,直到把数组分成 n 个小数组为止,(就是是把数组一分为二,分到都有序时为止!)

很明显这是一个递归地过程!不过,用迭代地方法也能做,我发现老师上课讲地迭代方法有问题,我也没改过来,对于本小白改这写个算法太费时间和脑子了。所以下面就直接放上递归地代码了!

/** * @brief Merge Sort * @param[in]   vec 原始数组 * @param[out]  l   当前处理的分段的左边界 * @param[out]  h   当前处理的分段的右边界 * *      Note:因为是递归嘛!所以必须知道当前在处理递归的那个分段 *            区间当前段的中点(左部分段的最后一个元素的索引)可以计算出来 mid = (l+h)/2; * */template <typename T>void MergeSortR(std::vector& vec, int l, int h){    /// 当左边界 小于 右边界时,说明分段中有超过1个元素!    if (l     {        int mid = (l+h)/2;        /// 左部分段的 最后一个元素的索引        MergeSortR(vec, l, mid);   /// 当前段 进入左半段的 activation record!        MergeSortR(vec, mid+1, h); /// 当前段 进入右半段的 activation record!        Merging(vec, l, mid, h);  /// 在returning phase中进行合并操作    }}

/// 把上面的那个递归操作封装起来template <typename T>void MergeSort(std::vector& vec){    MergeSortR(vec, 0, vec.size()-1);}

下图中的红色是递归的调用过程;蓝色是Merge排序的过程!

3. Quick Sort

阿西,这部分有点多,要是把笔记里的全写出来得累死了(笔记本上写了7页…),下面就说说主要的思路方法。

哦,对了,首先说明一下虽然这个排序算法的名字 中有 Quick 但是它并不是最快的!它时间复杂度是 O(nlogn)。还有时间复杂度是O(n)的排序算法,这比它块!

3.1 Idea Behind Quick Sort

操场上,老师要求一群学生按身高来排队,老师可以自己写张身高表来把他们按照身高顺序排好,但是这样比较慢!

一般情况下都是我们自己去看别人的身高,自己决定站哪的!就是每一个学生都要自己在心里拿自己的身高和别人的身高比对一下,自己找出自己的位置。这差不多就是QuickSort的思想!

学习一个单词:pivot:中轴线的意思。但是这里并不是指学生的排序就是在队伍的正中间,它只是表示当学生自己要找出他自己的位置时,他就要以自己为中心来与其他学生比较!所以每一个学生都可以是pivot在 pivot 之前的值都要小于等于 pivot, 在 pivot 之后的值都要大于 pivot!

下面放一段老师说的原话:Suppose you are standing in the queue and saying that I'm standing in my proper position,because all the people infront of me are shorter, and all the people at the back are taller. Whether they are sorted or not, that's not my business. I'm in my position!

3.2 Partitioning Procedure

就是找出一个元素他应该在的位置的过程,在这个元素之前的所有元素都小于等于它, 在这个元素之后的所有元素都大于它!

这就像把一个数组给分开了,小的放左边,大的放右边!所以就叫 Partitioning Procedure !

下面讲一下对一段范围内的第一元素找到其应该在那个位置上的思路,就是把一段范围内的数组的第一个元素当作是pivot,并把它放到它应该在的位置上去。

  1. 定义两个变量 i 和 j;i指向数组的开头,j指向数组的结尾(超尾);用一个循环,i不断地向右移动,j不断地向左移动。如果 j >= i时就停止(即:j 跑到 i 左边去了,说明遍历结束了!)

  2. 在上面的移动过程中:

  • 如果 i 处的值大于pivot,而且j处的值小于pivot,则交换一下 i 和 j上的值,++i, --j!继续。

  • 如果 i 处的值大于pivot,而且j处的值也大于pivot,则i不动,--j ! 继续。

  • 如果 i 处的值小于等于pivot,则++i,j不动!继续。

最后把第一个元素与第j个元素互换!

入下图所示,开始是 i 是指向50来着的,j指向最后一个元素(30)的后面,下图是直接执行了一步了,i 右移一个,j 左移一个。

上图画了一次 Partition 的整个流程!下面看代码也就好看了:

/** * @brief Partition操作,我这里用的是迭代器版本的 *      多加一个条件:i是一直在加的,所以要防止对end解引用,也就是到达h时,说明没找到大于 pivot 的 * @param[in] l 要进行 Partition 操作的区间的左边界 * @param[in] h 要进行 Partition 操作的区间的右边界(超尾) * * Note; 由于用的是迭代器,所以不用传递数组(vector)了 * */std::vector<int>::iterator partition_ite(std::vector<int>::iterator l, std::vector<int>::iterator h){    int pivot = *l;      /// 把第一个元素作为 pivot    auto i = l, j = h;   /// i从左边界开始,j 从右边界开始

    /// 循环,如果 i 上的值大于pivot,说明它应该放到pivot的后面;如果i上的值小于等于pivot,说明i上的值放这是没问题的,所以i++向右移动一位    ///       如果 j 上的值小于pivot。说明它应该放到pivot的前面;如果j上的值大于pivot,说明j上的值放这是没问题的,所以j--向左移动一位    ///       交换i和j上的值,就把小于pivot的放前面,大于pivot的放后面了!    do{        do{++i;}while (i!=h && *i <= pivot);  // i 加到超尾也结束(h表示超尾)!        do{--j;}while (*j > pivot);        if (i std::swap(*i, *j);    }while (i     /// 最后把pivot和j处互换。这样就使得pivot之前的都小于或等于pivot,pivot之后的都大于pivot    std::swap(*l, *j);    return j;  /// 要返回一下最终的pivot的实际位置!}

上面的 partition函数是把范围内的第一个元素作为 pivot的,经过partition函数之后,数组会被分为两段:partition position 之前的一段和其之后的一段都并未排序。所以我们继续对其进行 partition ,各段就能在被一分为2。这样循环做下去,直到左边界等于右边界(即:分到最后变成单个元素了),此时整个数组就是有序的了!所以就得出了Quick Sort了!

3.3 Quick Sort

不多解释了!对当前段partition,然后进入左半段的递归,再进入右半段的递归!完事!

void QuickSort_ite(std::vector<int>::iterator l, std::vector<int>::iterator h){    std::vector<int>::iterator j;    if (l     {        j = partition_ite(l, h);    /// 一定先 partition 才能分成两段        QuickSort_ite(l, j);        /// 进入左半段的递归        QuickSort_ite(j+1, h);   /// 进入右半段的递归    }}


上面讲了三种时间复杂度为O(nlogn)的排序算法!还有一个Tree Sort我没学。
下面讲三种时间复杂度为O(n)的算法

4. Count Sort

思路:

  • 创建一个额外的数组B并将其元素初始化为0,这个数组的 长度 是待排序数组A中的 最大的元素值加一

  • 把A中的元素值看作是B数组的下标,遍历A,让B相应位置上的元素加1(A的元素看作是B的索引)。

  • 最后遍历B,这样就能有序地输出A中的元素了!

让计数数组的长度为最大值+1 是因为数组的下标是从0开始的!所以为了让这个最大值成为计数数组的索引所以必须要+1 不然就会发生数组越界的错误!

下面的算法用用了冒泡排序法来找出数组中第一大的数(只做了一次冒泡的过程!后面会介绍)

/** * @brief Count Sort 只能用于整数了,所以就不用模板了 * */void CountSort(std::vector<int>& vec){    /// 找出原数组中的 第一大 值    int max = BubbleSort_K_Max(vec, 1);  /// 后面介绍    /// 因为下标从0开始,所以要多加一个1,才能让最后一个下标为max    std::vector<int> count(max+1, 0);

    /// 标记 计数数组中的值    for (int i : vec)        count.at(i)++;

    int i = 0;  /// 用于遍历计数数组    int j = 0;  /// 用于原数组,    while (i 1)   /// 遍历计数数组    {        if (count.at(i) > 0)  /// 如果计数大于0,说明有多个重复值,这多个重复值都要放回去        {            vec.at(j++) = i;  /// 把下标还给原数组            count.at(i)--;    /// 计数 减1(放回一个计数减一下,方便下轮循环判断还有没有重复的了)        }        else            i++;    }

}

5. Bin/Bucket Sort

这个和上面的Count Sort的思想差不多,不过这里不是计数了,是把相同的元素用链表链接起来,放在那个计数数组相应的位置上。如下图:

所以首先声明一个链表的结构体:

template <typename T>struct Node{    T data;    Node* next;explicit Node(int val, Node* n = nullptr) : data(val), next(n){}    ~Node(){delete next;}/// 拷贝构造,拷贝赋值就不写了!正规点是要写的,毕竟 Big Three};

思路是一样的思路,只不过把计数换成了链表的操作!链表的操作,这里就不多说了,我也发过一篇链表的推送!

void BinSort(std::vector<int>& vec){    /// 找出数组中的最大值    int max = BubbleSort_K_Max(vec, 1);    /// 创建 Bin;每个元素时一个 Node(初始化为dummy Node 方便插入操作))    std::vectorint>*> Bins(max+1, nullptr);for (auto & Bin : Bins)    {        Bin = new Node<int>(0);    }/// 遍历原数组,向桶中插入节点for (int i : vec)    {/// 插入节点(需要循环走到头再插入)auto* node = new Node<int>(i);  // 节点的值为原数组的值 //auto* p = Bins.at(i);           // 节点放在Buck对应的位置上 //while (p->next)  /// 如果下一个节点非空。            p = p->next;        p->next = node;  /// 插入节点    }/// 遍历桶int i = 0;int j = 0;  /// 原数组的下标计数器while (i 1)    {/// 一个小桶下可能有多个节点,所以也是和Count Sort那一样,拿出一个就从小桶中删掉一个重复的节点while (Bins.at(i)->next != nullptr)        {/// 删除节点(p用于前进 遍历链表,q用于跟随p 在最后对其next置空)auto* q = Bins.at(i);  // 节点放在Buck对应的位置上(哑元) //auto* p = Bins.at(i);  // 节点放在Buck对应的位置上(哑元) //while (p->next)  /// 如果下一个节点非空。            {                q = p;                p = p->next;            }            vec.at(j++) = p->data;delete p;            q->next = nullptr;        }        i++;    }}

6. Radix Sort

Radix是进制的意思。顾名思义,这种排序算法肯定是和进制有关的!

  • 其实这个也是类似于上面的Bin/Bucket Sort的,不过这个是它的改进版本!Radix Sort不用过多的额外数组空间,只需要开辟10个空间就行。

  • 那么数组有10个元素,其下标就是 0 ~ 9。0 ~ 9 又是十进制数字 每一位 上的范围!

  • 我们从 个位 开始,对所有元素的每一位进行比较,当前位上的数字是几,就把当前的元素放在第几个格子中(也是要用链表链接起来)!

首先要做的事就是研究下怎么获得第 i 位上的数字,得找一个公式出来:我把 个位 当作是第 0 位,那么第 i 位的公式如下:

我们还是需要找出数组中的最大元素,判断它共有几位。其实也不用判断,用一个while循环,我们每次对它整除10 直到 0 为止!在每次循环中我们都对元素的当前位进行比较,判断它应该放在哪个格子中,这样当while循环结束时也就排好序了!

下面看一下整个排序的过程:

还有一点需要注意的是,我们每次从格子的链表中拿出元素时,是需要按照先进先出的原则!就是我第一个放进去的是谁,那么第一个拿出来的也是谁!
另外,对于 233 和 13 这种的,位数不一致的,就把没有的位看作是0,比如13的百位是0!

void InsertNode(Node<int>* head, int val){    /// 插入节点    auto* node = new Node<int>(val);  // 节点的值为原数组的值 //    auto* p = head;           // 节点放在Buck对应的位置上 //

    while (p->next)  /// 如果下一个节点非空。        p = p->next;    p->next = node;  /// 插入节点}

/// 删除的是挂载的 第一个 值(并返回删除的节点的值)int DeleteNode(Node<int>* head){    int val = -1; /// 返回值,这种的只能用于正整数,所以返回 负1 表示没有元素

    /// head是哑节点,head->next是实际中的第一个值    auto firstNode = head->next;    if (firstNode)    {        val = firstNode->data;        head->next = firstNode->next;        return val;    }    return val;}

/** * 只能用于对整数进行排序,但是也可以改一改应用的场景,比如说字符串的长度就是整数 * */void RadixSort(std::vector<int>& vec){    /// 找出数组中的最大值(用于确定要经过几次Pass,即要确定最大的是几位数)    int max = BubbleSort_K_Max(vec, 1);  // 传的是引用,会改变内部的顺序 //

    /// 创建 Bin,只需要10个空间(0~9);每个元素时一个 Node(初始化为dummy Node 方便插入操作))    std::vectorint>*> Bins(10, nullptr);for (auto & Bin : Bins)    {        Bin = new Node<int>(0);    }int i = 1;  /// 用于 每次对元素整除 10^(i-1)次方 再 取 模 获得第i位上的数(个位开始)while (max != 0)  /// Max 每次都会整除 10 最后回变为零,所以,这里 Max 有几位数就会循环几次    {/// 遍历原数组,向桶中插入节点for (int ele : vec)        {/// i-1 表示元素的第几位(i-1 从0开始)int ind = ( ele / (int)std::pow(10, i-1) ) % 10;  /// 这个 i 要更新!最后会更新的  (pow 要 #include)            InsertNode(Bins.at(ind), ele);        }/// 遍历桶,再赋值回数组中int j = 0;  /// 原数组的 下标 for (int ind = 0; ind 10; ++ind)        {/// 每一个小格子中可能会有多个元素,也就是链表有多个节点!while (Bins.at(ind)->next != nullptr)            {int val = DeleteNode(Bins.at(ind));                vec.at(j++) = val;            }        }        max = max / 10;        i++; /// 进行下一位的比较    }}

下面对这个Radix Sort测试一下。


还剩三个O(n^2)的排序算法,简单讲讲把

7. Bubble Sort

It will compare a consecutive pair of elements every time。就是每次比较相邻的两个元素如果第一个比第二个大,那么就互换一下,这样就一次有一次的把最大的值放到最后面了。

  • 一次冒泡的过程就把最大值放到了最下面,换句话说就是:第一次冒泡就能找出最大值!

  • 那么第 K 次 冒泡就能找出第K大的值!

整个过程就是一个两层循环,没啥好说的!

/** * @brief Bubble Sort *      每轮冒泡都把最大值放到后面去,我这是从上向下比较的 *      更有趣的是: *              第 一 轮冒泡获得 第一大 的值 *              第 二 轮冒泡获得 第二大 的值 *              ........ *              第 K 轮冒泡获得  第K大  的值 *      所以,我们可以通过冒泡排序法获得第K 大的值(不过这个冒泡必须是从上向下比较的) *      这样值用排序 k 次就能获得第K大的值 * */template <typename T, class Compare = std::greater<>>void BubbleSort(std::vector& vec){    for (int i = 0; i /// 多少个元素冒泡多少次    {        for (int j = 0; j -1 -i; j++)  /// 终止条件 多 减 i 是因为后 i 个都排好序了        {            Compare c;            if (c(vec.at(j), vec.at(j+1)))                std::swap(vec.at(j), vec.at(j+1));        }    }}

/** * @brief 第 K 大的元素 *      通过冒泡排序法;冒泡 K 轮就行,不过要求从上向下冒泡 *      把 greater 改成 less 就是 第 K 小 * */template <typename T, class Compare = std::greater<>>T BubbleSort_K_Max(std::vector& vec, int k){    for (int i = 0; i /// 冒泡 K 轮    {        for (int j = 0; j -1 -i; ++j)  /// 终止条件 多 减 i 是因为后 i 个都排好序了        {            Compare c;            if (c(vec.at(j), vec.at(j+1)))                std::swap(vec.at(j), vec.at(j+1));        }    }    return vec.at(vec.size() - k);}

/** * @brief 第 K 小的元素 *      通过冒泡排序法;冒泡 K 轮就行,不过要求 从下向上 冒泡 *      从最后一个元素开始,如果后一个小于前一个就互换 * *      从下向上冒泡,每轮冒泡最小值都放到最上面去了 * *      这个就不放模板参数了 * */template <typename T>T BubbleSort_K_Min(std::vector& vec, int k){    for (int i = 0; i /// 冒泡 K 轮    {        for (int j = vec.size()-1; j > i; --j)  /// 前 i 个都是排好序的!所以 j > i 作为终止,避免额外的比较//        {            if (vec.at(j) -1))  /// 后一个小于前一个,就交换一下,气泡向上冒一下 //                std::swap(vec.at(j), vec.at(j-1));        }    }    return vec.at(k-1);  /// K 是从 1 开始的 //}

8. Selection Sort

就跟老师挨个给学生换座位差不多:

  • 从第一个座位开始,老师从班级里找出成绩最好的让他搬过来,也就是让原来坐在第一位的同学和第一名换个座位!

  • 同理,从班级里找出成绩第二的同学让他坐到第二个位子上来!

  • 后续同理。。。。

这就是选择排序的思想,从这个思想也可以看出:第一次选择排序可以找出第一大的元素,那么第K次 选择排序就能找出第K大的元素!

选择排序的过程如下!

不过这里是把最小的放在前面!

template <typename T, class Compare = std::greater<>>  ///模板模板参数中只能使用 class ,而且后面的类型不能重名 //void SelectionSort(std::vector& vec){    int i = 0; // 用于遍历整个数组 //    int j, k;  // j 和 k 用于遍历 i 后面的所有元素(与 i 上的元素进行比较,找出比 i 上元素还要小的元素)//    for (; i     {        /**         * k每轮开始时 指向和 i 相同;         * 开始时就假设i上的元素是最小的,如果后面找到比i上的元素还要小的,就让k指向这个更小的元素;         * 等到一轮的结束处k上的值与i上的值互换*/        k = i;        for (j = i; j         {            Compare c;            if (c(vec.at(k), vec.at(j)))  /// 模板参数要是换成less的话,就是由大到小排序了 //                k = j;        }        std::swap(vec.at(i), vec.at(k));    }}

template <typename T, class Compare = std::greater<>>  ///模板模板参数中只能使用 class ,而且后面的类型不能重名 //T SelectionSort_K(std::vector& vec, int K){    int i = 0; // 用于遍历整个数组 //    int j, k;  // j 和 k 用于遍历 i 后面的所有元素(与 i 上的元素进行比较,找出比 i 上元素还要小的元素)//    for (; i     {        /**         * k每轮开始时 指向和 i 相同;         * 开始时就假设i上的元素是最小的,如果后面找到比i上的元素还要小的,就让k指向这个更小的元素;         * 等到一轮的结束处k上的值与i上的值互换*/        k = i;        for (j = i; j         {            Compare c;            if (c(vec.at(k), vec.at(j)))  /// 模板参数要是换成less的话,就是由大到小排序了 //                k = j;        }        std::swap(vec.at(i), vec.at(k));    }    return vec.at(i-1);}

下面试一下找出第二大的元素

9. Insertion Sort

在这之前你需要了解一下向一个有序数组中插入一个元素!我就不写了,直接放笔记截图了

对于数组来说,你向其中插入一个元素是需要进行位移操作的,这个是挺费事的,加入你插入一个最小的元素,那么就需要把所有元素向左移动一位,如果数据没有空余的空间的话,还得重新开辟一堆内存,然后把值拷贝过去!

所以这个Insertion Sort不适用于数组的排序,它适用于链表的排序,因为链表不用进行移位操作!

但是下面的程序还是用的数组来说明这个算法!

这个开始时假装只有第一个元素在数组中,第二个到最后一个元素都是待插入的元素,一个元素的数组肯定是有序的!整个插入排序的过程如下:

代码中提供了链表的插入排序,具体就不说明了:

/// 链表节点template <typename T>struct Node{    T data;    Node* next;explicit Node(int val, Node* n = nullptr) : data(val), next(n){}    ~Node(){delete next;}/// 拷贝构造,拷贝赋值就不写了!正规点是要写的,毕竟 Big Three};/** * @brief 在有序数组中插入一个元素 *      从最后一个元素开始,如果要插入的值大于后面的元素,就把后面的元素向后移动一位,然后索引减一,接着比较 * */template <typename T>void InsertInSorted(std::vector& vec, T val, int n){/// n 表示数组的范围 0-n 索引范围内的是数组的值。但是vec的空间大于nint i = n;while (i > -1 && vec.at(i) > val)    {        vec.at(i+1) = vec.at(i);  /// 后移动一位        --i;    }    vec.at(i+1) = val;}/** * @brief 对链表插入 * */template <typename T>void InsertInSorted_Link(Node* dummy, T val){auto* p = dummy->next;auto* q = dummy;while (p && (val > p->data))    {        q = p;        p = p->next;    }/// 把新节点放在 q 和 p 之间!    q->next = new Node(val);    q->next->next = p;}/** * @brief Insertion Sort *      开始时假定第一个元素已经是拍好序的,第2个元素到最后一个元素都是作为待插入的元素,不算做序列中的东西 *      后面每次 Pass (完成一次插入的动作叫 Pass) 序列中的元素增加一个,待插入的元素减少一个。 *      也就是每插入一次,序列的长度增加 1 。 * */template <typename T>void InsertionSort(std::vector& vec){for (int i = 1; i     {        InsertInSorted(vec, vec.at(i), i-1);  // 0 ~ i-1  是每次插入后的序列的长度,i ~ vec.size() 是待插入的元素    }}/// 要是对链表排序的话,要用另一个链表来保存结果template <typename T>void InsertionSort_Link(std::initializer_list lst){/// 创建链表auto* dummy1 = new Node(0);    {        Node* p = dummy1;for (auto& ele : lst)        {auto* tmp = new Node(ele);            p->next = tmp;            p = tmp;        }    }/// 排序链表auto* head = dummy1->next->next;  /// 从第二个元素开始插入auto* dummy2 = new Node(0);    dummy2->next = new Node(dummy1->next->data);while (head)    {        InsertInSorted_Link(dummy2->next, head->data);        head = head->next;    }}


我靠终于写完了。。。。。。写到后面,前面的也记不得了。。。。还是得做好笔记随时查看!


排序 不用order by_Sort Techniques:介绍九种排序算法相关推荐

  1. 非常值得一看—九种滤波算法C语言实现

    关注"嵌入式软件开发学习圈"免费获取更多学习教程 今天带着大家学习滤波算法c语言(九种滤波算法)实现,以及代码,大家可以学习了解下.... 1.限幅滤波算法(程序判断滤波算法) 方 ...

  2. 【笔记】三张图读懂机器学习:基本概念、五大流派与九种常见算法

    文章目录 [笔记]三张图读懂机器学习:基本概念.五大流派与九种常见算法 Chapter 1: A look at Machine learning 1.What is it? 2.How does m ...

  3. Cortex-A7 MPCore 架构详细介绍(九种运行模式、内核寄存器组R0~R15,有特定的名字和功能)

    目录 0.ARM架构的历史简介 1.Cortex-A7 MPCore(即多核) 简介 2.Cortex-A 处理器九种运行模式 3.Cortex-A 寄存器组(内核寄存器) 3.1通用寄存器 3.1. ...

  4. c语言折半排序的程序,C语言实现九大排序算法的实例代码

    直接插入排序 将数组分为两个部分,一个是有序部分,一个是无序部分.从无序部分中依次取出元素插入到有序部分中.过程就是遍历有序部分,实现起来比较简单. #include void insertion_s ...

  5. 三张图读懂机器学习 :基本概念、五大流派与九种常见算法

    机器学习正在进步,我们似乎正在不断接近我们心中的人工智能目标.语音识别.图像检测.机器翻译.风格迁移等技术已经在我们的实际生活中开始得到了应用,但机器学习的发展仍还在继续,甚至被认为有可能彻底改变人类 ...

  6. 收藏 | 三张图读懂机器学习:基本概念、五大流派与九种常见算法

    点上方计算机视觉联盟获取更多干货 仅作学术分享,不代表本公众号立场,侵权联系删除 转载于:报道| 深度学习冲鸭   编辑|王萌 AI博士笔记系列推荐 周志华<机器学习>手推笔记正式开源!可 ...

  7. 【数据结构-排序】5.九种排序设计分析

  8. tail -f 查找关键字_C语言九种查找算法 | 总有一款适合你

    时间.空间复杂度比较 查找算法 平均时间复杂度 空间复杂度 查找条件 顺序查找 O(n) O(1) 无序或有序 二分查找(折半查找) O(log2n) O(1) 有序 插值查找 O(log2(log2 ...

  9. 从屌丝到高手,三道Python编程题,九种解题算法,看看你属于哪一类

    大家在平时刷题的过程中,不仅仅要注意理解问题的本质,而且要在解决问题的基础上,优化自己的解题思路和程序. 今天,小编就带领大家来进行三道简单问题的解决,从屌丝解法到进阶解法再到高手解法,一步步的带领大 ...

  10. 九种查找算法-哈希查找

    哈希查找算法又称散列查找算法,是一种借助哈希表(散列表)查找目标元素的方法,查找效率最高时对应的时间复杂度为 O(1). 哈希查找算法适用于大多数场景,既支持在有序序列中查找目标元素,也支持在无序序列 ...

最新文章

  1. 连连看路径求解的算法
  2. SAP HANA是什么
  3. [转]:xmake插件开发之色彩高亮显示
  4. 安卓系统内 的 安卓虚拟机
  5. Java 文件路劲获取(流的方式),适用与jar包或war包运行方式
  6. 简单servlet和jdbc回顾
  7. 统计学习方法第15章-奇异值分解SVD
  8. Linux下更改Python的软连接
  9. 阿里巴巴Java开发手册 PDF
  10. 南京邮电大学汇编——实验一:汇编语言语法练习与代码转换
  11. python3表白代码弹窗_抖音整蛊表白电脑弹窗代码大全
  12. 使用硕正插件在strtus2框架下返回数据问题
  13. 人物志-丘吉尔 Success consists of going from failure to failure without loss of enthusiasm. —— Winston Chu
  14. org.hibernate.QueryException: Unmatched braces for alias path 解决方案
  15. 【易购管理系统】导航折叠效果
  16. Access Token机制简单介绍
  17. JMF API 中文指导
  18. 西安公交车路线汇总(1)
  19. html js页面加载前执行,Javascript代码在页面加载时的执行顺序介绍
  20. matlab求同构数

热门文章

  1. python常用的库介绍_Python的标准库介绍与常用的第三方库
  2. 第二章 算法 (大话数据结构)
  3. BZOJ3457 : Ring
  4. Java并发包学习--ReentrantLock
  5. Windows Server 2003 R2中的DFS复制与管理
  6. 深入探討 SCOM 2007 管理技術
  7. String s = new String(“abc“)创建了几个对象
  8. Java十进制数和二进制数之间的相互转换
  9. python之Linux基础(三)
  10. U盘病毒“替身”大量交叉感染 打印店电脑助扩散