• C# 算法大全


    希尔排序

      希尔排序是将组分段,进行插入排序.对想提高C#语言编程能力的朋友,我们可以互相探讨一下。

      如:下面的程序,并没有实现多态,来,帮它实现一下。

    public class ShellSorter
        {
            public void Sort(int[] list)
            {
                int inc;
                for (inc = 1; inc <= list.Length / 9; inc = 3 * inc + 1) ;
                for (; inc > 0; inc /= 3)
                {
                    for (int i = inc + 1; i <= list.Length; i += inc)
                    {
                        int t = list[i - 1];
                        int j = i;
                        while ((j > inc) && (list[j - inc - 1] > t))
                        {
                            list[j - 1] = list[j - inc - 1];
                            j -= inc;
                        }
                        list[j - 1] = t;
                    }
                }
            }
        }
        public class MainClass
        {
            public static void Main()
            {
                int[] iArrary = new int[] { 1, 5, 3, 6, 10, 55, 9, 2, 87, 12, 34, 75, 33, 47 };
                ShellSorter sh = new ShellSorter();
                sh.Sort(iArrary);
                for (int m = 0; m <= 13; m++)
                    Console.WriteLine("{0}", iArrary[m]);
            }
        }
    View Code

    插入排序

     public class InsertionSorter
        {
            public void Sort(int[] list)
            {
                for (int i = 1; i < list.Length; ++i)
                {
                    int t = list[i];
                    int j = i;
                    while ((j > 0) && (list[j - 1] > t))
                    {
                        list[j] = list[j - 1];
                        --j;
                    }
                    list[j] = t;
                }
    
            }
        }
        public class MainClass
        {
            public static void Main()
            {
                int[] iArrary = new int[] { 1, 5, 3, 6, 10, 55, 9, 2, 87, 12, 34, 75, 33, 47 };
                InsertionSorter ii = new InsertionSorter();
                ii.Sort(iArrary);
                for (int m = 0; m <= 13; m++)
                    Console.WriteLine("{0}", iArrary[m]);
            }
        }
    View Code

    选择排序

    public class SelectionSorter
        {
            private int min;
            public void Sort(int[] list)
            {
                for (int i = 0; i < list.Length - 1; ++i)
                {
                    min = i;
                    for (int j = i + 1; j < list.Length; ++j)
                    {
                        if (list[j] < list[min])
                            min = j;
                    }
                    int t = list[min];
                    list[min] = list[i];
                    list[i] = t;
                    // Console.WriteLine("{0}",list[i]);
                }
    
            }
        }
        public class MainClass
        {
            public static void Main()
            {
                int[] iArrary = new int[] { 1, 5, 3, 6, 10, 55, 9, 2, 87, 12, 34, 75, 33, 47 };
                SelectionSorter ss = new SelectionSorter();
                ss.Sort(iArrary);
                for (int m = 0; m <= 13; m++)
                    Console.WriteLine("{0}", iArrary[m]);
    
            }
        }
    View Code

    ==============================================
      常见排序算法介绍

      1、稳定排序和非稳定排序

        简单地说就是所有相等的数经过某种排序方法后,仍能保持它们在排序之前的相对次序,我们就说这种排序方法是稳定的。反之,就是非稳定的。

        比如:一组数排序前是a1,a2,a3,a4,a5,其中a2=a4,经过某种排序后为a1,a2,a4,a3,a5,则我们说这种排序是稳定的,因为a2排序前在a4的前面,排序后它还是在a4

    的前面。假如变成a1,a4,a2,a3,a5就不是稳定的了。

      2、内排序和外排序

        在排序过程中,所有需要排序的数都在内存,并在内存中调整它们的存储顺序,称为内排序;在排序过程中,只有部分数被调入内存,并借助内存调整数在外存中的存放顺

    序排序方法称为外排序。

      3、算法的时间复杂度和空间复杂度

        所谓算法的时间复杂度,是指执行算法所需要的计算工作量。

        一个算法的空间复杂度,一般是指执行这个算法所需要的内存空间。
    ================================================================================
    */


    /*
    ================================================
    功能:选择排序
    输入:数组名称(也就是数组首地址)、数组中元素个数
    ================================================
    */
    /*
    ====================================================
    算法思想简单描述:

    在要排序的一组数中,选出最小的一个数与第一个位置的数交换;
    然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环
    到倒数第二个数和最后一个数比较为止。

    选择排序是不稳定的。算法复杂度O(n2)--[n的平方]
    =====================================================
    */
    void select_sort(int *x, int n)
    {
    int i, j, min, t;

    for (i=0; i<n-1; i++) /*要选择的次数:0~n-2共n-1次*/
    {
    min = i; /*假设当前下标为i的数最小,比较后再调整*/
    for (j=i+1; j<n; j++)/*循环找出最小的数的下标是哪个*/
    {
    if (*(x+j) < *(x+min))
    {
    min = j; /*如果后面的数比前面的小,则记下它的下标*/
    }
    }

    if (min != i) /*如果min在循环中改变了,就需要交换数据*/
    {
    t = *(x+i);
    *(x+i) = *(x+min);
    *(x+min) = t;
    }
    }
    }


    /*
    ================================================
    功能:直接插入排序
    输入:数组名称(也就是数组首地址)、数组中元素个数
    ================================================
    */
    /*
    ====================================================
    算法思想简单描述:

    在要排序的一组数中,假设前面(n-1) [n>=2] 个数已经是排
    好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数
    也是排好顺序的。如此反复循环,直到全部排好顺序。

    直接插入排序是稳定的。算法时间复杂度O(n2)--[n的平方]
    =====================================================
    */
    void insert_sort(int *x, int n)
    {
    int i, j, t;

    for (i=1; i<n; i++) /*要选择的次数:1~n-1共n-1次*/
    {
    /*
    暂存下标为i的数。注意:下标从1开始,原因就是开始时
    第一个数即下标为0的数,前面没有任何数,单单一个,认为
    它是排好顺序的。
    */
    t=*(x+i);
    for (j=i-1; j>=0 && t<*(x+j); j--) /*注意:j=i-1,j--,这里就是下标为i的数,在它前面有序列中找插入位置。*/
    {
    *(x+j+1) = *(x+j); /*如果满足条件就往后挪。最坏的情况就是t比下标为0的数都小,它要放在最前面,j==-1,退出循环*/
    }

    *(x+j+1) = t; /*找到下标为i的数的放置位置*/
    }
    }


    /*
    ================================================
    功能:冒泡排序
    输入:数组名称(也就是数组首地址)、数组中元素个数
    ================================================
    */
    /*
    ====================================================
    算法思想简单描述:

    在要排序的一组数中,对当前还未排好序的范围内的全部数,自上
    而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较
    小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要
    求相反时,就将它们互换。

    下面是一种改进的冒泡算法,它记录了每一遍扫描后最后下沉数的
    位置k,这样可以减少外层循环扫描的次数。

    冒泡排序是稳定的。算法时间复杂度O(n2)--[n的平方]
    =====================================================
    */

    void bubble_sort(int *x, int n)
    {
    int j, k, h, t;

    for (h=n-1; h>0; h=k) /*循环到没有比较范围*/
    {
    for (j=0, k=0; j<h; j++) /*每次预置k=0,循环扫描后更新k*/
    {
    if (*(x+j) > *(x+j+1)) /*大的放在后面,小的放到前面*/
    {
    t = *(x+j);
    *(x+j) = *(x+j+1);
    *(x+j+1) = t; /*完成交换*/
    k = j; /*保存最后下沉的位置。这样k后面的都是排序排好了的。*/
    }
    }
    }
    }


    /*
    ================================================
    功能:希尔排序
    输入:数组名称(也就是数组首地址)、数组中元素个数
    ================================================
    */
    /*
    ====================================================
    算法思想简单描述:

    在直接插入排序算法中,每次插入一个数,使有序序列只增加1个节点,
    并且对插入下一个数没有提供任何帮助。如果比较相隔较远距离(称为
    增量)的数,使得数移动时能跨过多个元素,则进行一次比较就可能消除
    多个元素交换。D.L.shell于1959年在以他名字命名的排序算法中实现
    了这一思想。算法先将要排序的一组数按某个增量d分成若干组,每组中
    记录的下标相差d.对每组中全部元素进行排序,然后再用一个较小的增量
    对它进行,在每组中再进行排序。当增量减到1时,整个要排序的数被分成
    一组,排序完成。

    下面的函数是一个希尔排序算法的一个实现,初次取序列的一半为增量,
    以后每次减半,直到增量为1。

    希尔排序是不稳定的。
    =====================================================
    */
    void shell_sort(int *x, int n)
    {
    int h, j, k, t;

    for (h=n/2; h>0; h=h/2) /*控制增量*/
    {
    for (j=h; j<n; j++) /*这个实际上就是上面的直接插入排序*/
    {
    t = *(x+j);
    for (k=j-h; (k>=0 && t<*(x+k)); k-=h)
    {
    *(x+k+h) = *(x+k);
    }
    *(x+k+h) = t;
    }
    }
    }


    /*
    ================================================
    功能:快速排序
    输入:数组名称(也就是数组首地址)、数组中起止元素的下标
    ================================================
    */
    /*
    ====================================================
    算法思想简单描述:

    快速排序是对冒泡排序的一种本质改进。它的基本思想是通过一趟
    扫描后,使得排序序列的长度能大幅度地减少。在冒泡排序中,一次
    扫描只能确保最大数值的数移到正确位置,而待排序序列的长度可能只
    减少1。快速排序通过一趟扫描,就能确保某个数(以它为基准点吧)
    的左边各数都比它小,右边各数都比它大。然后又用同样的方法处理
    它左右两边的数,直到基准点的左右只有一个元素为止。它是由
    C.A.R.Hoare于1962年提出的。

    显然快速排序可以用递归实现,当然也可以用栈化解递归实现。下面的
    函数是用递归实现的,有兴趣的朋友可以改成非递归的。

    快速排序是不稳定的。最理想情况算法时间复杂度O(nlog2n),最坏O(n2)

    =====================================================
    */
    void quick_sort(int *x, int low, int high)
    {
    int i, j, t;

    if (low < high) /*要排序的元素起止下标,保证小的放在左边,大的放在右边。这里以下标为low的元素为基准点*/
    {
    i = low;
    j = high;
    t = *(x+low); /*暂存基准点的数*/

    while (i<j) /*循环扫描*/
    {
    while (i<j && *(x+j)>t) /*在右边的只要比基准点大仍放在右边*/
    {
    j--; /*前移一个位置*/
    }

    if (i<j)
    {
    *(x+i) = *(x+j); /*上面的循环退出:即出现比基准点小的数,替换基准点的数*/
    i++; /*后移一个位置,并以此为基准点*/
    }

    while (i<j && *(x+i)<=t) /*在左边的只要小于等于基准点仍放在左边*/
    {
    i++; /*后移一个位置*/
    }

    if (i<j)
    {
    *(x+j) = *(x+i); /*上面的循环退出:即出现比基准点大的数,放到右边*/
    j--; /*前移一个位置*/
    }
    }

    *(x+i) = t; /*一遍扫描完后,放到适当位置*/
    quick_sort(x,low,i-1); /*对基准点左边的数再执行快速排序*/
    quick_sort(x,i+1,high); /*对基准点右边的数再执行快速排序*/
    }
    }


    /*
    ================================================
    功能:堆排序
    输入:数组名称(也就是数组首地址)、数组中元素个数
    ================================================
    */
    /*
    ====================================================
    算法思想简单描述:

    堆排序是一种树形选择排序,是对直接选择排序的有效改进。
    堆的定义如下:具有n个元素的序列(h1,h2,...,hn),当且仅当
    满足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1)(i=1,2,...,n/2)
    时称之为堆。在这里只讨论满足前者条件的堆。

    由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项。完全二叉树可以
    很直观地表示堆的结构。堆顶为根,其它为左子树、右子树。
    初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储顺序,
    使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点
    交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点
    的堆,并对它们作交换,最后得到有n个节点的有序序列。

    从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素
    交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数
    实现排序的函数。

    堆排序是不稳定的。算法时间复杂度O(nlog2n)。

    */
    /*
    功能:渗透建堆
    输入:数组名称(也就是数组首地址)、参与建堆元素的个数、从第几个元素开始
    */
    void sift(int *x, int n, int s)
    {
    int t, k, j;

    t = *(x+s); /*暂存开始元素*/
    k = s; /*开始元素下标*/
    j = 2*k + 1; /*右子树元素下标*/

    while (j<n)
    {
    if (j<n-1 && *(x+j) < *(x+j+1))/*判断是否满足堆的条件:满足就继续下一轮比较,否则调整。*/
    {
    j++;
    }

    if (t<*(x+j)) /*调整*/
    {
    *(x+k) = *(x+j);
    k = j; /*调整后,开始元素也随之调整*/
    j = 2*k + 1;
    }
    else /*没有需要调整了,已经是个堆了,退出循环。*/
    {
    break;
    }
    }

    *(x+k) = t; /*开始元素放到它正确位置*/
    }


    /*
    功能:堆排序
    输入:数组名称(也就是数组首地址)、数组中元素个数
    */
    void heap_sort(int *x, int n)
    {
    int i, k, t;
    int *p;

    for (i=n/2-1; i>=0; i--)
    {
    sift(x,n,i); /*初始建堆*/
    }

    for (k=n-1; k>=1; k--)
    {
    t = *(x+0); /*堆顶放到最后*/
    *(x+0) = *(x+k);
    *(x+k) = t;
    sift(x,k,0); /*剩下的数再建堆*/
    }
    }


    void main()
    {
    #define MAX 4
    int *p, i, a[MAX];

    /*录入测试数据*/
    p = a;
    printf("Input %d number for sorting :\n",MAX);
    for (i=0; i<MAX; i++)
    {
    scanf("%d",p++);
    }
    printf("\n");

    /*测试选择排序*/


    p = a;
    select_sort(p,MAX);
    /**/


    /*测试直接插入排序*/

    /*
    p = a;
    insert_sort(p,MAX);
    */


    /*测试冒泡排序*/

    /*
    p = a;
    insert_sort(p,MAX);
    */

    /*测试快速排序*/

    /*
    p = a;
    quick_sort(p,0,MAX-1);
    */

    /*测试堆排序*/

    /*
    p = a;
    heap_sort(p,MAX);
    */

    for (p=a, i=0; i<MAX; i++)
    {
    printf("%d ",*p++);
    }

    printf("\n");
    system("pause");
    }

    ============================================

    排序算法

    Copyright:bamboottm.my163.com //转载请保留出处

    计算机处理数据包括排序、检索(查找)、修改和删除操作。我们研究排序算法有几点充分理由。首先,是因为它实际应用非常频繁,计算机厂家…… //这个你要听吗? 不废话了

    //为了说明方便.定义如下数组: a:array[1..10] of integer;temp: 中间变量 排序: 从大到小

    l 选择排序
    1.基本的 选择排序
    <1>基本思想

    首先从要排序的数中选择最大的数,将它放在第一个位置,然后从剩下的数中选择最大的数放在第二个位置,如此继续,直到最后从剩下的两个数中选择最大的数放在倒数第二个位置,

    剩下的一个数放在最后位置,完成排序.

    下表是六个元素的排序的过程

     


    4 5 7 1 2 3

    ┗━━┛

    5 4 7 1 2 3
    ┗━━━━┛

    7 4 5 1 2 3
    ┗━━━━━━┛

    7 4 5 1 2 3

    ┗━━━━━━━━━━┛ 第一趟结束
    ⑦ 4 5 1 2 3

    ┗━┛
    7 5 4 1 2 3

    ┗━━━┛
    7 5 4 1 2 3
    ┗━━━━━┛
    7 5 4 1 2 3
    ┗━━━━━━━┛ 第二趟结束
    7 ⑤ 4 1 2 3
    ┗━┛

    7 5 4 1 2 3
    ┗━━━┛

    7 5 4 1 2 3

    ┗━━━━━┛ 第三趟结束

    7 5 ④ 1 2 3

    ┗━┛
    7 5 4 2 1 3 第四趟结束
    ┗━━━┛
    7 5 4 ③ 1 2
    ┗━┛ 第五趟结束

    7 5 4 3 ② ①


    <2>算法实现

    for i:=1 to 9 do

    for j:=i+1 to 10 do

    if a[i]<a[j]
    begin

    temp:=a[i];

    a[i]:=a[j];

    a[j]:=temp;

    end;

    2.改进

    以上排序方案每次交换两个元素需要执行三个语句,过多的交换必定要花费许多时间.改进方案是在内循环的比较中找出最大值元素的下标,在内循环结束时才考虑是否要调换.

    代码如下

    for i:=1 to 9 do

    begin

    k:=i;

    for j:=i+1 to 20 do

    if a[j]>a[k]

    then k:=j;

    if i<k {不可能大于}

    then begin

    temp:=a[i];

    a[i]:=a[k];

    a[k]:=temp;

    end;

    end;


    l 冒泡排序
    1.基本的冒泡排序
    <1> 基本思想

    依次比较相邻的两个数,把大的放前面,小的放后面.即首先比较第1个数和第2个数,大数放前,小数放后.然后比较第2个数和第3个数......直到比较最后两个数.第一趟结束,最小的

    一定沉到最后.重复上过程,仍从第1个数开始,到最后第2个数.然后......
    由于在排序过程中总是大数往前,小数往后,相当气泡上升,所以叫冒泡排序.

    下面是6个元素的排序的过程

     

     

    4 5 7 1 2 3

    ┗━━┛
    5 4 7 1 2 3
    ┗━━┛
    5 7 4 1 2 3
    ┗━━┛
    5 7 4 1 2 3

    ┗━━┛
    5 7 4 2 1 3

    ┗━━┛ 第一趟结束

    5 7 4 2 3 ①
    ┗━━┛

    7 5 4 2 3 1
    ┗━━┛

    7 5 4 2 3 1
    ┗━━┛
    7 5 4 2 3 1

    ┗━━┛ 第二趟结束
    7 5 4 3 ② 1

    ┗━━┛
    7 5 4 3 2 1
    ┗━━┛

    7 5 4 3 2 1
    ┗━━┛ 第三趟结束

    7 5 4 ③ 2 1
    ┗━━┛
    7 5 4 3 2 1
    ┗━━┛ 第四趟结束

    7 5 ④ 3 2 1
    ┗━━┛ 第五趟结束

    ⑦ ⑤ 4 3 2 1

    <2> 算法实现

    for i:=1 to 9 do

    for j:=1 to 10-i do

    if a[j]<a[j+1]

    then begin

    temp:=a[j];

    a[j]:=a[j+1];

    a[j+1]:=temp;

    end;

    2 改进

       上例中,可以发现,第二趟结束已经排好序.但是计算机此时并不知道已经排好序.所以,还需进行一次比较,如果没有发生任何数据交换,则知道已经排好序,可以不干了.因此第

    三趟比较还需进行,第四趟、第五趟比较则不必要.

    我们设置一个布尔变量bo 来记录是否有进行交换.值为false 进行了比较 true 则没有

    代码如下

    i:=1;
    repeat

    bo:=true;

    for j:=1 to 10-i

    if a[j]<a[j+1] then

    begin

    temp:=a[j];
    a[j]:=a[j+1];

    a[j+1]:=temp;

    bo:=false;

    end;

          inc(i);

    until bo;

    3.再次改进
    如果说是有20个元素.数据序列是8,3,4,9,7再后跟着15个大于9且已经排好序的数据.在第三趟后算法终止.总共做了19+18+17=54次比较使得绝大多数已排好序的数据在一遍

    扫描后足以发现他们是排好序的情况下仍然被检查3遍.

        我们改进如下
    flag:=10;

    while flag>0 do

    begin
    k:=flag-1;
    flag:=0;

    for i:=1 to k do

    if a[i]<a[i+1] then

    begin

    temp:=a[i];

    a[i]:=a[i+1];
    a[i+1]:=temp;

    flag:=i;

    end;

    end; 


    改进的冒泡算法对上述数据进行的比较次数是19+4+2=24.


    l 希尔排序
    <1> 基本思想

    希尔排序法是1959年由D.L.Shell提出来的,又称减少增量的排序。下表是以八个元素排序示范的例子.在该例中,开始时相隔4个成分,分别按组进行排序,这时每组2个成分,共4组;

    然后相隔2个成分,在按组排序......最后,对所有相邻成分进行排序.
    可参阅<<计算机程序设计技巧??第三卷排序查找


    <2> 算法实现

    j:=10;

    i:=1;

    while j>1 do

    begin

    j:=j div 2;

    repeat

    alldone:=true;

    for index:=1 to 10-j do

    begin

    i:=index+j;

    if a[index]<a[i] then

    begin

    temp:=a[index];

    a[index]:=a[i];

    a[i]:=temp;

    alldone:=false;

    end;

    end;

    until alldone

    end;

    //说句实话,这个很少有人用.:( 当然我也不会,书上抄的

    l 插入排序

    <1> 基本思想

    //对不起,我没书.所以是我自己讲.我很菜.不要介意
    插入排序的思想就是读一个,排一个.  //也许是这样,起码我是这么认为的:)

         将第1个数放入数组的第1个元素中,以后读入的数与已存入数组的数进行比较,确定它在从大到小的排列中应处的位置.将该位置以及以后的元素向后推移一个位置

    ,将读入的新数填入空出的位置中.
    <2> 算法实现  {加了读入语句}

    procedure insert(x,num:integer);

    var

    i,pos:integer;

    search:boolean;

    begin

    pos:=1;

    search:=true;

    while search and (pos<=num ) do

    if x>a[pos]

    then search:=fasle

    else inc(pos);

    for i:=num downto pos do

    a[i+1]:=a[i];

    a[pos]:=x;

    num:=num+1;

    end;

    num:=0 {当前数组的长度}

    for i:=1 to 10 do

    begin

    read(x);

    intert(x,num)        

    end;

         

    l 合并排序

    <1> 基本思想

    合并排序的算法就是二分法。
    分解:将n个元素分解成各含 一半元素的子序列。
    解决:用合并排序法对两个子序列递归地排序。
    合并:合并两个已排序的子序列排序结果。
    在对子序列排列时,当其长度为1时递归结束,因为单个元素被认为是已排好序的.合并排序的.合并排序的关键步骤在于合并目前产生的两个已排好序的子序列:
    A[p..q] 和 A[q+1…r];
    将它们合并成一个已排好序的子序列A[p..r]. 我们引入一个辅助过程merge(A,p,q,r)来完成这一项合并工作,其中A是数组,p,q,r是下标.

    <2> 算法实现


    procedure merge( p,q,r:integer);
    var
    i,j,t:integer;
    it:array[1..10] of integer;
    begin
    t:=p; i:=p; j:=q+1;
    while t<=r do
    begin
    if (i<=q) and ((j>j) or (a[i]<=a[j]))
    then begin
    it[t]:=a[i]; inc(i);
    end
    else begin
    it[t]:=a[j]; inc(j);
    end;
    inc(t);
    end;
    for i:=p to r do a[i]:=t[i];
    end;
    procedure merge_sort(p,r:integer);
    var q:integer;
    begin
    if p<>r then begin
    q:=(p+r-1) div 2 ;
    merge_sort(p,q);
    merge_sort(q+1,r);
    merge(p,q,r);
    end;
    end;
    begin
    merge_sort(1,10);
    end.
    l 快速排序

    <1> 基本思想

    快速排序的基本思想是基于分治策略的。对于输入的子序列L[p..r],如果规模足够小则直接进行排序,否则分三步处理:
    分解(Divide):将输入的序列L[p..r]划分成两个非空子序列L[p..q]和L[q+1..r],使L[p..q]中任一元素的值不大于L[q+1..r]中任一元素的值。
    递归求解(Conquer):通过递归调用快速排序算法分别对L[p..q]和L[q+1..r]进行排序。
    合并(Merge):由于对分解出的两个子序列的排序是就地进行的,所以在L[p..q]和L[q+1..r]都排好序后不需要执行任何计算L[p..r]就已排好序。
    这个解决流程是符合分治法的基本步骤的。因此,快速排序法是分治法的经典应用实例之一。


    ================================================================================

    排序算法是一种基本并且常用的算法。由于实际工作中处理的数量巨大,所以排序算法 对算法本身的速度要求很高。而一般我们所谓的算法的性能主要是指算法的复杂度,一般用

    O方法来表示。在后面我将 给出详细的说明。
    对 于排序的算法我想先做一点简单的介绍,也是给这篇文章理一个提纲。 我将按照算法的复杂度,从简单到难来分析算法。第一部分是简单排序算法,后面你将看到他们的共同

    点是算法复杂度为O(N*N)(因为没有使用word,所 以无法打出上标和下标)。第二部分是高级排序算法,复杂度为O(Log2(N))。这里我们只介绍一种算法。另外还有几种算法因为

    涉及树与堆的概念,所以 这里不于讨论。 第三部分类似动脑筋。这里的两种算法并不是最好的(甚至有最慢的),但是算法本身比较奇特,值得参考(编程的角度)。同时也可

    以让我们从另外的角度来认识 这个问题。现在,让我们开始吧:
    一、简单排序算法
    由于程序比较简单,所以没有加什么注释。所有的程序都给出了完整的运行代码,并在我的VC环境
    下运行通过。因为没有涉及MFC和WINDOWS的内容,所以在BORLAND C++的平台上应该也不会有什么
    问题的。在代码的后面给出了运行过程示意,希望对理解有帮助。
    1.冒泡法:
    这是最原始,也是众所周知的最慢的算法了。他的名字的由来因为它的工作看来象是冒泡:
    #include <iostream.h>
    void BubbleSort(int* pData,int Count)
    {
    int iTemp;
    for(int i=1;i<Count;i++)
    {
    for(int j=Count-1;j>=i;j--)
    {
    if(pData[j]<pData[j-1])
    {
    iTemp = pData[j-1];
    pData[j-1] = pData[j];
    pData[j] = iTemp;
    }
    }
    }
    }

    void main()
    {
    int data[] = {10,9,8,7,6,5,4};
    BubbleSort(data,7);
    for (int i=0;i<7;i++)
    cout<<data[i]<<" ";
    cout<<"\n";
    }

    倒序(最糟情况)
    第一轮:10,9,8,7->10,9,7,8->10,7,9,8->7,10,9,8(交换3次)
    第二轮:7,10,9,8->7,10,8,9->7,8,10,9(交换2次)
    第一轮:7,8,10,9->7,8,9,10(交换1次)
    循环次数:6次
    交换次数:6次
    其他:
    第一轮:8,10,7,9->8,10,7,9->8,7,10,9->7,8,10,9(交换2次)
    第二轮:7,8,10,9->7,8,10,9->7,8,10,9(交换0次)
    第一轮:7,8,10,9->7,8,9,10(交换1次)
    循环次数:6次
    交换次数:3次
    上面我们给出了程序段,现在我们分析它:这里,影响我们算法性能的主要部分是循环和交换,显然,次数越多,性能就越差。从上面的程序我们可以看出循环的次数是固定的,

    为1+2+...+n-1。写成公式就是1/2*(n-1)*n。 现在注意,我们给出O方法的定义:
    若存在一常量K和起点n0,使当n>=n0时,有f(n)<=K*g(n),则f(n) = O(g(n))。(呵呵,不要说没学好数学呀,对于编程数学是非常重要的!!!)

    现 在我们来看1/2*(n-1)*n,当K=1/2,n0=1,g(n)=n*n时,1/2*(n-1)*n<=1/2*n*n=K*g(n)。所以 f(n) =O(g(n))=O(n*n)。所以我们程序循环的复杂度为O(n*n)。再看交换。从程

    序后面所跟的表可以看到,两种情况的循环相同,交换不同。其实 交换本身同数据源的有序程度有极大的关系,当数据处于倒序的情况时,交换次数同循环一样(每次循环判断都

    会交换),复杂度为O(n*n)。当数据为正序, 将不会有交换。复杂度为O(0)。乱序时处于中间状态。正是由于这样的原因,我们通常都是通过循环次数来对比算法。
    2.交换法:
    交换法的程序最清晰简单,每次用当前的元素一一的同其后的元素比较并交换。
    #include <iostream.h>
    void ExchangeSort(int* pData,int Count)
    {
    int iTemp;
    for(int i=0;i<Count-1;i++)
    {
    for(int j=i+1;j<Count;j++)
    {
    if(pData[j]<pData[i])
    {
    iTemp = pData[i];
    pData[i] = pData[j];
    pData[j] = iTemp;
    }
    }
    }
    }

    void main()
    {
    int data[] = {10,9,8,7,6,5,4};
    ExchangeSort(data,7);
    for (int i=0;i<7;i++)
    cout<<data[i]<<" ";
    cout<<"\n";
    }
    倒序(最糟情况)
    第一轮:10,9,8,7->9,10,8,7->8,10,9,7->7,10,9,8(交换3次)
    第二轮:7,10,9,8->7,9,10,8->7,8,10,9(交换2次)
    第一轮:7,8,10,9->7,8,9,10(交换1次)
    循环次数:6次
    交换次数:6次

    其他:
    第一轮:8,10,7,9->8,10,7,9->7,10,8,9->7,10,8,9(交换1次)
    第二轮:7,10,8,9->7,8,10,9->7,8,10,9(交换1次)
    第一轮:7,8,10,9->7,8,9,10(交换1次)
    循环次数:6次
    交换次数:3次

    从运行的表格来看,交换几乎和冒泡一样糟。事实确实如此。循环次数和冒泡一样也是1/2*(n-1)*n,所以算法的复杂度仍然是O(n*n)。由于我们无法给出所有的情况,所以只能直

    接告诉大家他们在交换上面也是一样的糟糕(在某些情况下稍好,在某些情况下稍差)。

    3.选择法:
    现在我们终于可以看到一点希望:选择法,这种方法提高了一点性能(某些情况下)这种方法类似我们人为的排序习惯:从数据中选择最小的同第一个值交换,在从省下的部分中

    选择最小的与第二个交换,这样往复下去。
    #include <iostream.h>
    void SelectSort(int* pData,int Count)
    {
    int iTemp;
    int iPos;
    for(int i=0;i<Count-1;i++)
    {
    iTemp = pData[i];
    iPos = i;
    for(int j=i+1;j<Count;j++)
    {
    if(pData[j]<iTemp)
    {
    iTemp = pData[j];
    iPos = j;
    }
    }
    pData[iPos] = pData[i];
    pData[i] = iTemp;
    }
    }

    void main()
    {
    int data[] = {10,9,8,7,6,5,4};
    SelectSort(data,7);
    for (int i=0;i<7;i++)
    cout<<data[i]<<" ";
    cout<<"\n";
    }
    倒序(最糟情况)
    第一轮:10,9,8,7->(iTemp=9)10,9,8,7->(iTemp=8)10,9,8,7->(iTemp=7)7,9,8,10(交换1次)
    第二轮:7,9,8,10->7,9,8,10(iTemp=8)->(iTemp=8)7,8,9,10(交换1次)
    第一轮:7,8,9,10->(iTemp=9)7,8,9,10(交换0次)
    循环次数:6次
    交换次数:2次

    其他:
    第一轮:8,10,7,9->(iTemp=8)8,10,7,9->(iTemp=7)8,10,7,9->(iTemp=7)7,10,8,9(交换1次)
    第二轮:7,10,8,9->(iTemp=8)7,10,8,9->(iTemp=8)7,8,10,9(交换1次)
    第一轮:7,8,10,9->(iTemp=9)7,8,9,10(交换1次)
    循环次数:6次
    交换次数:3次
    遗 憾的是算法需要的循环次数依然是1/2*(n-1)*n。所以算法复杂度为O(n*n)。我们来看他的交换。由于每次外层循环只产生一次交换(只有一个最 小值)。所以f(n)<=n 所以我

    们有f(n)=O(n)。所以,在数据较乱的时候,可以减少一定的交换次数。

    4.插入法:
    插入法较为复杂,它的基本工作原理是抽出牌,在前面的牌中寻找相应的位置插入,然后继续下一张
    #include <iostream.h>
    void InsertSort(int* pData,int Count)
    {
    int iTemp;
    int iPos;
    for(int i=1;i<Count;i++)
    {
    iTemp = pData[i];
    iPos = i-1;
    while((iPos>=0) && (iTemp<pData[iPos]))
    {
    pData[iPos+1] = pData[iPos];
    iPos--;
    }
    pData[iPos+1] = iTemp;
    }
    }

    void main()
    {
    int data[] = {10,9,8,7,6,5,4};
    InsertSort(data,7);
    for (int i=0;i<7;i++)
    cout<<data[i]<<" ";
    cout<<"\n";
    }

    倒序(最糟情况)
    第一轮:10,9,8,7->9,10,8,7(交换1次)(循环1次)
    第二轮:9,10,8,7->8,9,10,7(交换1次)(循环2次)
    第一轮:8,9,10,7->7,8,9,10(交换1次)(循环3次)
    循环次数:6次
    交换次数:3次

    其他:
    第一轮:8,10,7,9->8,10,7,9(交换0次)(循环1次)
    第二轮:8,10,7,9->7,8,10,9(交换1次)(循环2次)
    第一轮:7,8,10,9->7,8,9,10(交换1次)(循环1次)
    循环次数:4次
    交换次数:2次

    上 面结尾的行为分析事实上造成了一种假象,让我们认为这种算法是简单算法中最好的,其实不是,因为其循环次数虽然并不固定,我们仍可以使用O方法。从上面的 结果可以看

    出,循环的次数f(n)<= 1/2*n*(n-1)<=1/2*n*n。所以其复杂度仍为O(n*n)(这里说明一下,其实如果不是为了展示这些简单排序的不同,交换次数仍然 可以这样推导)。现在看

    交换,从外观上看,交换次数是O(n)(推导类似选择法),但我们每次要进行与内层循环相同次数的‘=’操作。正常的一次交换我们 需要三次‘=’ 而这里显然多了一些,所以

    我们浪费了时间。

    最终,我个人认为,在简单排序算法中,选择法是最好的。

    二、高级排序算法:
    高 级排序算法中我们将只介绍这一种,同时也是目前我所知道(我看过的资料中)的最快的。它的工作看起来仍然象一个二叉树。首先我们选择一个中间值 middle程序中我们使

    用数组中间值,然后把比它小的放在左边,大的放在右边(具体的实现是从两边找,找到一对后交换)。然后对两边分别使用这个过程 (最容易的方法——递归)。
    1.快速排序:
    #include <iostream.h>
    void run(int* pData,int left,int right)
    {
    int i,j;
    int middle,iTemp;
    i = left;
    j = right;
    middle = pData[(left+right)/2]; //求中间值
    do{
    while((pData[i]<middle) && (i<right))//从左扫描大于中值的数
    i++;
    while((pData[j]>middle) && (j>left))//从右扫描大于中值的数
    j--;
    if(i<=j)//找到了一对值
    {
    //交换
    iTemp = pData[i];
    pData[i] = pData[j];
    pData[j] = iTemp;
    i++;
    j--;
    }
    }while(i<=j);//如果两边扫描的下标交错,就停止(完成一次)

    //当左边部分有值(left<j),递归左半边
    if(left<j)
    run(pData,left,j);
    //当右边部分有值(right>i),递归右半边
    if(right>i)
    run(pData,i,right);
    }
    void QuickSort(int* pData,int Count)
    {
    run(pData,0,Count-1);
    }
    void main()
    {
    int data[] = {10,9,8,7,6,5,4};
    QuickSort(data,7);
    for (int i=0;i<7;i++)
    cout<<data[i]<<" ";
    cout<<"\n";
    }
    这里我没有给出行为的分析,因为这个很简单,我们直接来分析算法:首先我们考虑最理想的情况
    1.数组的大小是2的幂,这样分下去始终可以被2整除。假设为2的k次方,即k=log2(n)。
    2.每次我们选择的值刚好是中间值,这样,数组才可以被等分。
    第一层递归,循环n次,第二层循环2*(n/2)......
    所以共有n+2(n/2)+4(n/4)+...+n*(n/n) = n+n+n+...+n=k*n=log2(n)*n
    所以算法复杂度为O(log2(n)*n)
    其 他的情况只会比这种情况差,最差的情况是每次选择到的middle都是最小值或最大值,那么他将变成交换法(由于使用了递归,情况更糟)。但是你认为这种 情况发生的几率

    有多大??呵呵,你完全不必担心这个问题。实践证明,大多数的情况,快速排序总是最好的。如果你担心这个问题,你可以使用堆排序,这是一种 稳定的O(log2(n)*n)算法,但

    是通常情况下速度要慢于快速排序(因为要重组堆)。

    三、其他排序
    1.双向冒泡:
    通常的冒泡是单向的,而这里是双向的,也就是说还要进行反向的工作。 代码看起来复杂,仔细理一下就明白了,是一个来回震荡的方式。写这段代码的作者认为这样可以在冒泡

    的基础上减少一些交换(我不这么认为,也许我错了)。反正我认为这是一段有趣的代码,值得一看。
    #include <iostream.h>
    void Bubble2Sort(int* pData,int Count)
    {
    int iTemp;
    int left = 1;
    int right =Count -1;
    int t;
    do {
    //正向的部分
    for(int i=right;i>=left;i--)
    {
    if(pData[i]<pData[i-1])
    {
    iTemp = pData[i];
    pData[i] = pData[i-1];
    pData[i-1] = iTemp;
    t = i;
    }
    }
    left = t+1;
    //反向的部分
    for(i=left;i<right+1;i++)
    {
    if(pData[i]<pData[i-1])
    {
    iTemp = pData[i];
    pData[i] = pData[i-1];
    pData[i-1] = iTemp;
    t = i;
    }
    }
    right = t-1;
    }while(left<=right);
    }

    void main()
    {
    int data[] = {10,9,8,7,6,5,4};
    Bubble2Sort(data,7);
    for (int i=0;i<7;i++)
    cout<<data[i]<<" ";
    cout<<"\n";
    }

    2.SHELL排序
    这个排序非常复杂,看了程序就知道了。 首先需要一个递减的步长,这里我们使用的是9、5、3、1(最后的步长必须是1)。工作原理是首先对相隔9-1个元素的所有内容排序,然

    后再使用同样的方法对相隔5-1个元素的排序,以次类推。
    #include <iostream.h>
    void ShellSort(int* pData,int Count)
    {
    int step[4];
    step[0] = 9;
    step[1] = 5;
    step[2] = 3;
    step[3] = 1;
    int i,Temp;
    int k,s,w;
    for(int i=0;i<4;i++)
    {
    k = step[i];
    s = -k;
    for(int j=k;j<Count;j++)
    {
    iTemp = pData[j];
    w = j-k;//求上step个元素的下标
    if(s ==0)
    {
    s = -k;
    s++;
    pData[s] = iTemp;
    }
    while((iTemp<pData[w]) && (w>=0) && (w<=Count))
    {
    pData[w+k] = pData[w];
    w = w-k;
    }
    pData[w+k] = iTemp;
    }
    }
    }

    void main()
    {
    int data[] = {10,9,8,7,6,5,4,3,2,1,-10,-1};
    ShellSort(data,12);
    for (int i=0;i<12;i++)
    cout<<data[i]<<" ";
    cout<<"\n";
    }
    呵 呵,程序看起来有些头疼。不过也不是很难,把s==0的块去掉就轻松多了,这里是避免使用0 步长造成程序异常而写的代码。这个代码我认为很值得一看。这个算法的得名是因

    为其发明者的名字D.L.SHELL。依照参考资料上的说法:“由于复杂的数 学原因避免使用2的幂次步长,它能降低算法效率。”另外算法的复杂度为n的1.2次幂。同样因为非常复杂

    并 “超出本书讨论范围”的原因(我也不知道过程),我们只有结果了.
        

  • 相关阅读:
    mac的webdriver自动化
    MongoDB win安装后无法远程连接访问
    Fiddler的一些坑: !SecureClientPipeDirect failed: System.IO.IOException
    Mac终端用Sublime打开指定文件或文件夹
    [Spring常见问题]java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener
    Flask_Flask-Mail邮件扩展(十三)
    Flask_Flask-Migrate数据迁移扩展(十二)
    Flask + flask_sqlalchemy + jq 完成书籍展示、新增、删除功能
    Flask_CSRF保护(十一)
    SQLAlchemy(十)
  • 原文地址:https://www.cnblogs.com/xchit/p/1888879.html
Copyright © 2020-2023  润新知