算法系列15天速成 第二天 七大经典排序【中】

首先感谢朋友们对第一篇文章的鼎力支持,感动中....... 

今天说的是选择排序,包括“直接选择排序”和“堆排序”。

话说上次“冒泡排序”被快排虐了,而且“快排”赢得了内库的重用,众兄弟自然眼红,非要找快排一比高下。

这不今天就来了两兄弟找快排算账。

1.直接选择排序:

先上图:

说实话,直接选择排序最类似于人的本能思想,比如把大小不一的玩具让三岁小毛孩对大小排个序,

那小孩首先会在这么多玩具中找到最小的放在第一位,然后找到次小的放在第二位,以此类推。。。。。。

,小孩子多聪明啊,这么小就知道了直接选择排序。羡慕中........

对的,小孩子给我们上了一课,

第一步: 我们拿80作为参照物(base),在80后面找到一个最小数20,然后将80跟20交换。

第二步:  第一位数已经是最小数字了,然后我们推进一步在30后面找一位最小数,发现自己最小,不用交换。

第三步:........

最后我们排序完毕。大功告成。

既然是来挑战的,那就5局3胜制。

代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;

namespace SelectionSort
{
    public class Program
    {
        static void Main(string[] args)
        {
            //5次比较
            for (int i = 1; i <= 5; i++)
            {
                List<int> list = new List<int>();

//插入2w个随机数到数组中
                for (int j = 0; j < 20000; j++)
                {
                    Thread.Sleep(1);
                    list.Add(new Random((int)DateTime.Now.Ticks).Next(1000, 1000000));
                }

Console.WriteLine("\n第" + i + "次比较:");

Stopwatch watch = new Stopwatch();

watch.Start();
                var result = list.OrderBy(single => single).ToList();
                watch.Stop();

Console.WriteLine("\n快速排序耗费时间:" + watch.ElapsedMilliseconds);
                Console.WriteLine("输出前十个数:" + string.Join(",", result.Take(10).ToList()));

watch.Start();
                result = SelectionSort(list);
                watch.Stop();

Console.WriteLine("\n直接选择排序耗费时间:" + watch.ElapsedMilliseconds);
                Console.WriteLine("输出前十个数:" + string.Join(",", list.Take(10).ToList()));

}
        }

//选择排序
        static List<int> SelectionSort(List<int> list)
        {
            //要遍历的次数
            for (int i = 0; i < list.Count - 1; i++)
            {
                //假设tempIndex的下标的值最小
                int tempIndex = i;

for (int j = i + 1; j < list.Count; j++)
                {
                    //如果tempIndex下标的值大于j下标的值,则记录较小值下标j
                    if (list[tempIndex] > list[j])
                        tempIndex = j;
                }

//最后将假想最小值跟真的最小值进行交换
                var tempData = list[tempIndex];
                list[tempIndex] = list[i];
                list[i] = tempData;
            }
            return list;
        }
    }
}

比赛结果公布:

堆排序:

要知道堆排序,首先要了解一下二叉树的模型。

下图就是一颗二叉树,具体的情况我后续会分享的。

那么堆排序中有两种情况(看上图理解):

大根堆:  就是说父节点要比左右孩子都要大。

小根堆:  就是说父节点要比左右孩子都要小。

那么要实现堆排序,必须要做两件事情:

第一:构建大根堆。

首先上图:

首先这是一个无序的堆,那么我们怎样才能构建大根堆呢?

第一步: 首先我们发现,这个堆中有2个父节点(2,,3);

第二步: 比较2这个父节点的两个孩子(4,5),发现5大。

第三步: 然后将较大的右孩子(5)跟父节点(2)进行交换,至此3的左孩子堆构建完毕,

如图:

第四步: 比较第二个父节点(3)下面的左右孩子(5,1),发现左孩子5大。

第五步: 然后父节点(3)与左孩子(5)进行交换,注意,交换后,堆可能会遭到破坏,

必须按照以上的步骤一,步骤二,步骤三进行重新构造堆。

最后构造的堆如下:

第二:输出大根堆。

至此,我们把大根堆构造出来了,那怎么输出呢?我们做大根堆的目的就是要找出最大值,

那么我们将堆顶(5)与堆尾(2)进行交换,然后将(5)剔除根堆,由于堆顶现在是(2),

所以破坏了根堆,必须重新构造,构造完之后又会出现最大值,再次交换和剔除,最后也就是俺们

要的效果了,

发现自己兄弟被别人狂殴,,堆排序再也坐不住了,决定要和快排干一场。

同样,快排也不甘示弱,谁怕谁?

代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;

namespace HeapSort
{
    public class Program
    {
        static void Main(string[] args)
        {
            //5次比较
            for (int j = 1; j <= 5; j++)
            {
                List<int> list = new List<int>();

//插入2w个数字
                for (int i = 0; i < 20000; i++)
                {
                    Thread.Sleep(1);
                    list.Add(new Random((int)DateTime.Now.Ticks).Next(1000, 100000));
                }

Console.WriteLine("\n第" + j + "次比较:");

Stopwatch watch = new Stopwatch();
                watch.Start();
                var result = list.OrderBy(single => single).ToList();
                watch.Stop();
                Console.WriteLine("\n快速排序耗费时间:" + watch.ElapsedMilliseconds);
                Console.WriteLine("输出前十个数" + string.Join(",", result.Take(10).ToList()));

watch = new Stopwatch();
                watch.Start();
                HeapSort(list);
                watch.Stop();
                Console.WriteLine("\n堆排序耗费时间:" + watch.ElapsedMilliseconds);
                Console.WriteLine("输出前十个数" + string.Join(",", list.Take(10).ToList()));
            }

}

///<summary>
/// 构建堆
///</summary>
///<param name="list">待排序的集合</param>
///<param name="parent">父节点</param>
///<param name="length">输出根堆时剔除最大值使用</param>
        static void HeapAdjust(List<int> list, int parent, int length)
        {
            //temp保存当前父节点
            int temp = list[parent];

//得到左孩子(这可是二叉树的定义,大家看图也可知道)
            int child = 2 * parent + 1;

while (child < length)
            {
                //如果parent有右孩子,则要判断左孩子是否小于右孩子
                if (child + 1 < length && list[child] < list[child + 1])
                    child++;

//父亲节点大于子节点,就不用做交换
                if (temp >= list[child])
                    break;

//将较大子节点的值赋给父亲节点
                list[parent] = list[child];

//然后将子节点做为父亲节点,已防止是否破坏根堆时重新构造
                parent = child;

//找到该父亲节点较小的左孩子节点
                child = 2 * parent + 1;
            }
            //最后将temp值赋给较大的子节点,以形成两值交换
            list[parent] = temp;
        }

///<summary>
/// 堆排序
///</summary>
///<param name="list"></param>
        public static void HeapSort(List<int> list)
        {
            //list.Count/2-1:就是堆中父节点的个数
            for (int i = list.Count / 2 - 1; i >= 0; i--)
            {
                HeapAdjust(list, i, list.Count);
            }

//最后输出堆元素
            for (int i = list.Count - 1; i > 0; i--)
            {
                //堆顶与当前堆的第i个元素进行值对调
                int temp = list[0];
                list[0] = list[i];
                list[i] = temp;

//因为两值交换,可能破坏根堆,所以必须重新构造
                HeapAdjust(list, 0, i);
            }
        }
    }
}

结果公布:

堆排序此时心里很尴尬,双双被KO,心里想,一定要捞回面子,一定要赢,

于是堆排序提出了求“前K大问题”。(就是在海量数据中找出前几大的数据),

快排一口答应,小意思,没问题。

双方商定,在2w随机数中找出前10大的数:

代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;

namespace QuickSort
{
    public class Program
    {
        static void Main(string[] args)
        {
            //5此比较
            for (int j = 1; j <= 5; j++)
            {
                List<int> list = new List<int>();

for (int i = 0; i < 20000; i++)
                {
                    Thread.Sleep(1);
                    list.Add(new Random((int)DateTime.Now.Ticks).Next(1000, 100000));
                }

Console.WriteLine("\n第" + j + "次比较:");

Stopwatch watch = new Stopwatch();
                watch.Start();
                var result = list.OrderByDescending(single => single).Take(10).ToList();
                watch.Stop();
                Console.WriteLine("\n快速排序求前K大耗费时间:" + watch.ElapsedMilliseconds);
                Console.WriteLine("输出前十个数:" + string.Join(",", result.Take(10).ToList()));

watch = new Stopwatch();
                watch.Start();
                result = HeapSort(list, 10);
                watch.Stop();
                Console.WriteLine("\n堆排序求前K大耗费时间:" + watch.ElapsedMilliseconds);
                Console.WriteLine("输出前十个数:" + string.Join(",", list.Take(10).ToList()));
            }

}

///<summary>
/// 构建堆
///</summary>
///<param name="list">待排序的集合</param>
///<param name="parent">父节点</param>
///<param name="length">输出根堆时剔除最大值使用</param>
        static void HeapAdjust(List<int> list, int parent, int length)
        {
            //temp保存当前父节点
            int temp = list[parent];

//得到左孩子(这可是二叉树的定义哇)
            int child = 2 * parent + 1;

while (child < length)
            {
                //如果parent有右孩子,则要判断左孩子是否小于右孩子
                if (child + 1 < length && list[child] < list[child + 1])
                    child++;

//父节点大于子节点,不用做交换
                if (temp >= list[child])
                    break;

//将较大子节点的值赋给父亲节点
                list[parent] = list[child];

//然后将子节点做为父亲节点,已防止是否破坏根堆时重新构造
                parent = child;

//找到该父节点左孩子节点
                child = 2 * parent + 1;
            }
            //最后将temp值赋给较大的子节点,以形成两值交换
            list[parent] = temp;
        }

///<summary>
/// 堆排序
///</summary>
///<param name="list">待排序的集合</param>
///<param name="top">前K大</param>
///<returns></returns>
        public static List<int> HeapSort(List<int> list, int top)
        {
            List<int> topNode = new List<int>();

//list.Count/2-1:就是堆中非叶子节点的个数
            for (int i = list.Count / 2 - 1; i >= 0; i--)
            {
                HeapAdjust(list, i, list.Count);
            }

//最后输出堆元素(求前K大)
            for (int i = list.Count - 1; i >= list.Count - top; i--)
            {
                //堆顶与当前堆的第i个元素进行值对调
                int temp = list[0];
                list[0] = list[i];
                list[i] = temp;

//最大值加入集合
                topNode.Add(temp);

//因为顺序被打乱,必须重新构造堆
                HeapAdjust(list, 0, i);
            }
            return topNode;
        }
    }
}

求前K大的输出结果:

最后堆排序赶紧拉着直接选择排序一路小跑了,因为求前K大问题已经不是他原本来的目的。

ps: 直接选择排序的时间复杂度为:O(n^2)

堆排序的时间复杂度:O(NlogN)

(0)

相关推荐

  • 算法系列15天速成——第十三天 树操作【下】

    听说赫夫曼胜过了他的导师,被认为"青出于蓝而胜于蓝",这句话也是我比较欣赏的,嘻嘻. 一  概念 了解"赫夫曼树"之前,几个必须要知道的专业名词可要熟练记住啊. 1: 结点的权 "权"就相当于"重要度",我们形象的用一个具体的数字来表示,然后通过数字的大小来决定谁重要,谁不重要. 2: 路径 树中从"一个结点"到"另一个结点"之间的分支. 3: 路径长度 一个路径上的分支数量. 4: 树

  • 算法系列15天速成 第八天 线性表【下】

    一:线性表的简单回顾 上一篇跟大家聊过"线性表"顺序存储,通过实验,大家也知道,如果我每次向顺序表的头部插入元素,都会引起痉挛,效率比较低下,第二点我们用顺序存储时,容易受到长度的限制,反之就会造成空间资源的浪费. 二:链表 对于顺序表存在的若干问题,链表都给出了相应的解决方案. 1. 概念:其实链表的"每个节点"都包含一个"数据域"和"指针域". "数据域"中包含当前的数据. "指针域"

  • 算法系列15天速成 第十一天 树操作(上)

    先前我们讲的都是"线性结构",他的特征就是"一个节点最多有一个"前驱"和一个"后继".那么我们今天讲的树会是怎样的呢? 我们可以对"线性结构"改造一下,变为"一个节点最多有一个"前驱"和"多个后继".哈哈,这就是我们今天说的"树". 一: 树 我们思维中的"树"就是一种枝繁叶茂的形象,那么数据结构中的"树"该

  • 算法系列15天速成 第三天 七大经典排序【下】

    直接插入排序: 这种排序其实蛮好理解的,很现实的例子就是俺们斗地主,当我们抓到一手乱牌时,我们就要按照大小梳理扑克,30秒后, 扑克梳理完毕,4条3,5条s,哇塞......  回忆一下,俺们当时是怎么梳理的. 最左一张牌是3,第二张牌是5,第三张牌又是3,赶紧插到第一张牌后面去,第四张牌又是3,大喜,赶紧插到第二张后面去, 第五张牌又是3,狂喜,哈哈,一门炮就这样产生了. 怎么样,生活中处处都是算法,早已经融入我们的生活和血液. 下面就上图说明:              看这张图不知道大家可

  • 算法系列15天速成——第十五天 图【下】(大结局)

    今天是大结局,说下"图"的最后一点东西,"最小生成树"和"最短路径". 一: 最小生成树 1. 概念 首先看如下图,不知道大家能总结点什么. 对于一个连通图G,如果其全部顶点和一部分边构成一个子图G1,当G1满足: ① 刚好将图中所有顶点连通.②顶点不存在回路.则称G1就是G的"生成树". 其实一句话总结就是:生成树是将原图的全部顶点以最小的边连通的子图,这不,如下的连通图可以得到下面的两个生成树. ② 对于一个带权的连通图,

  • 算法系列15天速成 第九天 队列

    一:概念 队列是一个"先进先出"的线性表,牛X的名字就是"First in First Out(FIFO)",生活中有很多这样的场景,比如读书的时候去食堂打饭时的"排队".当然我们拒绝插队. 二:存储结构 前几天也说过,线性表有两种"存储结构",① 顺序存储,②链式存储.当然"队列"也脱离不了这两种服务,这里我就分享一下"顺序存储". 顺序存储时,我们会维护一个叫做"head头

  • 算法系列15天速成 第十四天 图【上】

    今天来分享一下图,这是一种比较复杂的非线性数据结构,之所以复杂是因为他们的数据元素之间的关系是任意的,而不像树那样 被几个性质定理框住了,元素之间的关系还是比较明显的,图的使用范围很广的,比如网络爬虫,求最短路径等等,不过大家也不要胆怯, 越是复杂的东西越能体现我们码农的核心竞争力. 既然要学习图,得要遵守一下图的游戏规则. 一: 概念 图是由"顶点"的集合和"边"的集合组成.记作:G=(V,E): <1> 无向图 就是"图"中的边没

  • 算法系列15天速成 第十天 栈

    一: 概念 栈,同样是一种特殊的线性表,是一种Last In First Out(LIFO)的形式,现实中有很多这样的例子, 比如:食堂中的一叠盘子,我们只能从顶端一个一个的取. 二:存储结构 "栈"不像"队列",需要两个指针来维护,栈只需要一个指针就够了,这得益于栈是一种一端受限的线性表. 这里同样用"顺序结构"来存储这个"栈",top指针指向栈顶,所有的操作只能在top处. 代码段: 复制代码 代码如下: #region

  • 算法系列15天速成 第五天 五大经典查找【中】

    哈希查找: 对的,他就是哈希查找,说到哈希,大家肯定要提到哈希函数,呵呵,这东西已经在我们脑子里面形成固有思维了.大家一定要知道"哈希"中的对应关系.     比如说: "5"是一个要保存的数,然后我丢给哈希函数,哈希函数给我返回一个"2",那么此时的"5"和"2"就建立一种对应关系,这种关系就是所谓的"哈希关系",在实际应用中也就形成了"2"是key,"5

  • 算法系列15天速成 第一天 七大经典排序【上】

    针对现实中的排序问题,算法有七把利剑可以助你马道成功. 首先排序分为四种:       交换排序: 包括冒泡排序,快速排序.      选择排序: 包括直接选择排序,堆排序.      插入排序: 包括直接插入排序,希尔排序.      合并排序: 合并排序. 那么今天我们讲的就是交换排序,我们都知道,C#类库提供的排序是快排,为了让今天玩的有意思点,我们设计算法来跟类库提供的快排较量较量.争取KO对手. 冒泡排序: 首先我们自己来设计一下"冒泡排序",这种排序很现实的例子就是:我抓一

  • 算法系列15天速成 第七天 线性表【上】

    哈哈,我们的数据也一样,存在这三种基本关系,用术语来说就是: <1>  线性关系.<2>  树形关系.<3>  网状关系. 一: 线性表 1 概念:                 线性表也就是关系户中最简单的一种关系,一对一.                  如:学生学号的集合就是一个线性表. 2 特征:                 ① 有且只有一个"首元素".                 ② 有且只有一个"末元素".

  • 算法系列15天速成 第十二天 树操作【中】

    先前说了树的基本操作,我们采用的是二叉链表来保存树形结构,当然二叉有二叉的困扰之处,比如我想找到当前结点的"前驱"和"后继",那么我们就必须要遍历一下树,然后才能定位到该"节点"的"前驱"和"后继",每次定位都是O(n),这不是我们想看到的,那么有什么办法来解决呢? (1) 在节点域中增加二个指针域,分别保存"前驱"和"后继",那么就是四叉链表了,哈哈,还是有点浪费空

  • 算法系列15天速成 第四天 五大经典查找【上】

    在我们的算法中,有一种叫做线性查找. 分为:顺序查找.        折半查找. 查找有两种形态: 分为:破坏性查找,   比如有一群mm,我猜她们的年龄,第一位猜到了是23+,此时这位mm已经从我脑海里面的mmlist中remove掉了. 哥不找23+的,所以此种查找破坏了原来的结构. 非破坏性查找, 这种就反之了,不破坏结构. 顺序查找: 这种非常简单,就是过一下数组,一个一个的比,找到为止. 复制代码 代码如下: using System;using System.Collections.

  • 算法系列15天速成 第六天 五大经典查找【下】

    大家是否感觉到,树在数据结构中大行其道,什么领域都要沾一沾,碰一碰.就拿我们前几天学过的排序就用到了堆和今天讲的"二叉排序树",所以偏激的说,掌握的树你就是牛人了. 今天就聊聊这个"五大经典查找"中的最后一个"二叉排序树". 1. 概念:     <1> 其实很简单,若根节点有左子树,则左子树的所有节点都比根节点小.                             若根节点有右子树,则右子树的所有节点都比根节点大.     &

随机推荐