zoukankan      html  css  js  c++  java
  • 常见的排序算法——选择排序

    一、直接选择排序

    #include <iostream>
    using namespace std;
    
    void print_array(int a[], int n)
    {
        for(int i = 0; i < n; i++)
            cout << a[i]  << " " ;
        cout << endl;
    }
    
    void Swap(int &a, int &b)
    {    
        if(a != b)
        {
            a ^= b;
            b ^= a;
            a ^= b;
        }
    }
    
    void select_direct(int a[], int n)
    {
        
        for(int i = 0; i < n; i++)
        {
            int middle = i;
            for(int j = i+1; j < n; j++)    
            {
                if(a[middle] >= a[j])
                {
                    middle = j;
                }
            }
            Swap(a[middle],a[i]);
        }
    }
    
    int main()
    {
        int a[] = {5,4,9,8,2,7,0,1,6,3};
        cout << "before sort:";
        print_array(a, 10);
    
        select_direct(a, 10);
        cout << "after sort :";
        print_array(a, 10);
        
        return 0;
    }

    说明:

      直接选择排序基本思想:第一次从a[0]~a[n-1]中选出最小值,与a[0]交换;第二次从a[1]~a[n-1]选出最小值,与a[1]交换;......第n-1次从a[n-2]~a[n-1]选出最小值,与a[n-2]交换,总共n-1次得到有序序列。

    二、堆排序

    (参考:

    https://www.cnblogs.com/JVxie/p/4859889.html  (这篇通俗易懂,但是弹出部分讲的不清晰,有代码实现)

    https://www.jianshu.com/p/6b526aa481b1 (这篇比上篇讲的清晰很多,没有代码实现)

      堆概念:堆是用数组实现的二叉树,它没有父指针和子指针。可以被视为一个完全二叉树。

      那什么是完全二叉树呢?——若设二叉树的深度为h,除第 h 层外,其它各层 (1h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。

      现在画两种完全二叉树:根据节点的序号不同,父节点和子节点的序号关系也会不同。根据对应关系,存储树就只需要一个简单的数组,不需要额外的空间了。

      堆分为两种类型:最大堆和最小堆。在最大堆中,父节点的值比每一个子节点的值都要大。在最小堆中,父节点的值比每一个子节点的值都要小。上图的堆属于最大堆。这种最大堆和最小堆的属性非常有用,因为堆常常被当做优先队列使用,因为可以快速的访问到“最重要”的元素。

      那么,如果一个堆不是最大/小堆,我们该如何去维护?

      有以下几个操作: 

    1. 上浮 shift_up
    2. 下沉 shift_down
    3. 插入 push
    4. 弹出 pop
    5. 取顶 top
    6. 堆排序 heap_sort

      例子图示:

     

      如果要将上图的树堆化(Heapify)成最小堆。可以看出:根节点1(value = 8)的子节点3(value = 2)比根节点1小,所以应该交换节点3和节点1。但是节点7(value = 1)的值更小,这时候我们是无法直接和根节点交换的,那我们就需要一个操作来实现这个交换过程,那就是上浮 shift_up

      1.上浮 shift_up 操作过程如下:

      从当前结点开始,和它的父节点比较,若是比父亲节点小,就交换,然后将当前节点下标更新为原父亲节点下标;否则退出。

      模拟操作图示:  

    伪代码如下:

    Shift_up( i )
    {
        while( i / 2 >= 1)
        {
            if( 堆数组名[ i ] < 堆数组名[ i/2 ] )
            {
                swap( 堆数组名[ i ] , 堆数组名[ i/2 ]) ;
                i = i / 2;
            }
            else break;
    }

      这样,(value = 1)的节点就到了根节点1。但是还有一个问题:节点3(value = 8)比子节点6(value = 7)和子节点7(value = 2)都大,它需要和子节点交换,但是应该和哪一个子节点交换呢?答案是更小的那个。

      2.所以就有了下沉操作 shift_down:

      让当前结点的左右子节点(如果有的话)作比较,哪个比较小就和它交换,并更新当前节点的下标为被交换的子节点下标,否则退出。 

      模拟操作图示: 

    伪代码如下:

    Shift_down( i , n )    //n表示当前有n个节点
    {
        while( i * 2 <= n)  //存在左节点
        {
            T = i * 2 ;
            if( T + 1 <= n && 堆数组名[ T + 1 ] < 堆数组名[ T ])  //存在右节点 && 右节点 < 左节点
                T++;
            if( 堆数组名[ i ] < 堆数组名[ T ] )
            {
               swap( 堆数组名[ i ] , 堆数组名[ T ] );
                i = T;
            }
            else break;
    }

    3.插入 push 操作:在数组的最后一个插入。如果要维护树的结构,插入后要有上浮 shift_up 操作。

     (例子参考 https://www.jianshu.com/p/6b526aa481b1)   

      该堆的数组是:{10, 7, 2, 5, 1},现在将16插入到堆中,数组变成{10, 7, 2, 5, 1, 16},对应的堆变成右图。(该例为最大堆

      现在需要交换16和2,然后交换16和10,使得插入的节点(value = 16)比父节点小或者到达根节点。这就是上浮 shift_up 操作

      最后得到堆:

                                         

    4.弹出 pop 操作:弹出堆顶元素,但是弹出后仍然需要维护堆的属性(最大堆/最小堆)。那么此时该怎么做呢?

    具体做法让根节点元素和尾节点进行交换,然后让现在的根元素下沉就可以了!

     伪代码:

    Pop ( x )
        {
            swap( 堆数组名[1] , 堆数组名[ n ] );
            n--;          //这一步很关键
            Shift_down( 1 );
        }

    5.取顶 top 操作:直接返回数组下标[1]的元素。

    注意判断堆内是否还有元素。

    6.堆排序 heap_sort 操作:开一个新的数组,每次取堆顶元素放入,然后把堆顶元素弹出。

    伪代码:

    Heap_sort( a[] )
    {
            k=0;
            while( size > 0 )
            {
                k++;
                a[ k ] = top();
                pop();    
            }        
    }

      堆排序的时间复杂度是O(nlogn)理论上是十分稳定的,但是对于我们来说并没有什么卵用。

      我们要排序的话,直接使用快排即可,时间更快,用堆排还需要O(2*n)空间。这也是为什么我说堆的操作时间复杂度在O(1)~O(logn)。

      讲完到这里,堆也基本介绍完了,那么它有什么用呢??

      举个粒子,比如当我们每次都要取某一些元素的最小值,而取出来操作后要再放回去,重复做这样的事情。

      我们若是用快排的话,最坏的情况需要O(q*n^2),而若是堆,仅需要O(q*logn),时间复杂度瞬间低了不少。

    上述源码可参考:https://www.cnblogs.com/JVxie/p/4859889.html

    另一个代码实现:

    //这里序号是从0开始的,所以left=2i + 1; right = left + 1; parent = (i-1)/2
    #include <iostream>
    using namespace std;
    
    void PrintArray(int a[], int n)
    {
        for(int i = 0; i < n; i++)
            cout << a[i]  << " " ;
        cout << endl;
    }
    
    //构建最大堆,保证堆顶元素为最大
    void MaxHeapify(int a[], int i, int size)
    {
        int l = 2*i+1, r = l + 1;
        int largest = 0;
        
        if((l < size) && (a[l] > a[i]))
            largest = l;
        else
            largest = i;
        if((r < size) && (a[r] > a[largest]))
            largest = r;
    
        if(largest != i)
        {
            int tmp = a[i];
            a[i] = a[largest];
            a[largest] = tmp;
    //cout << "exchange" <<endl;
    //PrintArray(a, size);
            MaxHeapify(a, largest, size);
        }
    //cout << "MaxHeapify" <<endl;
    //PrintArray(a, size);
    }
    
    //建堆:从i=(size-1)/2开始,调用MaxHeapify 直到i=0
    void BuildMaxHeap(int a[], int size)
    {
        for(int i = (size-1) / 2; i >= 0; i--)    
        {
            MaxHeapify(a, i, size);
        }
    }
    
    void HeapSort(int a[], int size)
    {
        BuildMaxHeap(a, size);
    cout << "build over:" << endl; 
        PrintArray(a, size);
    
        int len = size;
        for(int i = size-1; i > 0; i--)
        {
            int tmp = a[i];
            a[i] = a[0];
            a[0] = tmp;    
            len--;
            MaxHeapify(a, 0, len);        
        }    
    }
    
    int main()
    {
        int a[100] = {1,2,3,4,5,6,7,8,9};
        int size=9;
    
        cout << "input  :" << endl; 
        PrintArray(a, size);
        HeapSort(a, size);
        cout << "sort over  :" << endl; 
        PrintArray(a, size);
    
        return 0;
    }
  • 相关阅读:
    Ubuntu中php.ini修改运行内存
    Parse error: syntax error, unexpected end of file in * 的解决办法
    php函数积累
    php值传递和引用传递
    kohana导入和导出
    gleez框架获得时间控件
    php获得时间段的月
    图片垂直居中代码
    三角代码
    HTML5 SVG可爱笑脸动画
  • 原文地址:https://www.cnblogs.com/Brickert/p/10763824.html
Copyright © 2011-2022 走看看