算法初探 之 排序算法

原创 2007年09月27日 21:32:00
摘《李开复:算法的力量》:算法是计算机科学领域最重要的基石之一,但却受到了国内一些程序员的冷落。许多学生看到一些公司在招聘时要求的编程语言五花八门就产生了一种误解,认为学计算机就是学各种编程语言,或者认为,学习最新的语言、技术、标准就是最好的铺路方法。其实大家都被这些公司误导了。编程语言虽然该学,但是学习计算机算 法和理论更重要,因为计算机算法和理论更重要,因为计算机语言和开发平台日新月异,但万变不离其宗的是那些算法和理论,例如数据结构、算法、编译原理、计算机体系结构、关系型数据库原理等等

许多计算机科学家认为,排序算法是算法学习中最基本的问题。那么我们就从排序这类算法开始,去看看算法究竟是什么。

排序算法解决的是一类什么具体问题呢?

输入:n个数的序列<a1,a2,a3,...,an>

输出:输入序列的一个重排<a'1,a'2,a'3,...,a'n>,使得a'1<=a'2<=a'3<=...<=a'n

输入序列通常是一个n元数组,但也可能由其他形式来表示,如链表。

对于这个问题,我们可以很容易联想到日常生活中的许多例子。那么我们解决这个问题的第一个思路便可以从日常生活中获得。

插入排序,这是一个对少量元素进行排序的有效算法。其工作机理和很多人打牌时,整理手中牌时的做法差不多。在开始摸牌时,我们的左手是空的,牌面朝下的放在桌上。接着,一次从桌上摸起一张牌,并将它插入到左手一把牌中的正确位置上。为了找到这张牌合适的位置,要将它与手中已有的每一张牌从右向左地进行比较。无论在什么时候,左手中的牌都是排好序的,而这些牌原先都是桌上那副牌里最顶上的一些牌。

//主方法

INSERTION-SORT(A)

    //从桌面上一次次取牌

     for j ← 2 to length[A]

    do

                    //取一张牌到右手key

                       key ← A[j]

                                //眼睛注意的位置(将要比较的位置)

                                i ← j – 1

                                //开始寻找合适的位置

                                while i > 0 and A[i] > key

                                do

                                        //微调左手中的已经排好顺序的牌

          A[i + 1] ← A[i]

          //移动眼睛,转换比较的对象

                                        i ← i – 1

                                        //将右手中的牌放在左手牌中合适的位置上

                                 A[i + 1] ← key

 

大概的思路已经在脑子中形成了,剩下的是简单的工作:将思路转化为实实在在的代码。在这里我用java编写了一个静态方法。关于这个算法的具体证明过程详见《Introduction to Algorithms.

 /**

*InsertionSort

*The time limit of this sort is O(n^2)

*<strong>O(n^2)</strong>

*
@param element will be sorted

*在这段代码中使用了java的范型以及一个对象接口,详细情况可以参见java相关教材

*/


public static <Type extends Comparable> void insertionSort(Type element[]){

for(int j=1;j<element.length;j++){

Type key 
= element[j];

int i = j-1;

while( (i>=0)&&(element[i].compareTo(key)>0)){

element[i
+1= element[i];

i
--;

}


element[i
+1= key;

}


}

如同大家打牌一样,很多人熟能生巧整理手中的牌时又准又快,而有些人却费时费力。那么一个算法也是友好有坏,在这里我只给出相关的评价指数,至于具体规定可参见相关书籍。

这个算法的时间复杂度为O(n^2),空间复杂度为O(n).关于“O”符号可以在微积分类书籍上找到更为详尽的解释。

科学技术不断进步,很多新的算法应运而生。在这里再介绍一种排序算法。它在时间复杂度上有了具大提高,空间上却付出了两倍的代价。

合并排序,一种利用了“分治策略”的排序方法。分治策略:将原问题划分成n个规模较小而结构简单与原问题相似的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。排序算法就是借助了这种思想,本质是:从中间把数组分成元素个数尽量相等的两半,分别对他们进行排序,然后再合并两个有序表。整个问题最困难的地方便是合并两个有序表,在这里我们着重看看这个过程。

再举打牌这个例子,假设有两堆牌正面朝上地放在桌上,每一堆都是已经排好顺序的,最小的牌在最上面。我们希望把这两堆牌合并成一个排好序的输出堆,面朝下地放在桌上。基本步骤包括在面朝上的两堆牌中,选取顶上两张牌中较小的一张,将其取出后面朝下地放入输出堆中。重复这个步骤,直到某一堆为空为止。

    /**

     *Merge used in the mergeSort

     *<strong>O(n)</strong>

     *
@param element Type[] the elements to be merged

     *
@param p int the begin of the  first elements

     *
@param q int the end of the first elements

     *
@param r int the end of the second elements

     
*/


    
private static <Type extends Comparable> void merge(Type element[],int p,int q,int r){

           
//确定两堆牌的起始位置
        int nl    =    q-p+1;

        
int nr    =    r-q;

        

           
//新建两个输入堆用于存放待组合牌
        Type[] lElement,rElement;

        lElement    
=    (Type[])java.lang.reflect.Array.newInstance(element.getClass().getComponentType(),nl);

        rElement    
=    (Type[])java.lang.reflect.Array.newInstance(element.getClass().getComponentType(),nr);

                

           
//做记录处理
        for(int i=0;i<nl;i++){

            lElement[i]    
=    element[p+i];

        }


        
for(int i=0;i<nr;i++){

            rElement[i]    
=    element[q+i+1];

        }


        

           
//开始取牌
        int i=0,j=0;

        
int k    =    p;

           
//逐个比较,一直到一个堆为空
        while((i<nl)&&(j<nr)){

            
if(lElement[i].compareTo(rElement[j])<=0){

                element[k]    
=    lElement[i];

                i
++;

            }
else{

                element[k]    
=    rElement[j];

                j
++;

            }


            k
++;

        }


        

           
//处理某一堆中 剩下的牌
        while(i<nl){

            element[k]    
=    lElement[i];

            i
++;

            k
++;

        }


        
while(j<nr){

            element[k]    
=    rElement[j];

            j
++;

            k
++;

        }


    }



这个算法的时间复杂度为O(n log2 n),空间复杂度为O(2n)O(n).

至此两个算法的介绍结束,我们将这类算法扩大化,从中取出根本的东西。

逆序对

逆序对是指在一个元素序列中,按照一定的大小比较方法,序列中任两个元素大小顺序颠倒的组合。

A[1..n]是一个包含n个不同数的数组.如果在i<j的情况下,A[i]>A[j],(i,j)就称为A中的一个逆序对.

对于一个给定待排序序列,其中的逆序对的发现与还原正是排序所要解决的事情.排序过程中一般是先发现逆序对再将其还原,由于这些让我们联想到了排序性能提高的一些方法.

1.一次还原操作,使得多组逆序对还原.

eg: 共有21对 共有10

7,6,5,4,3,2,1--(17互换)-->1,6,5,4,3,2,7

一次还原处理了11

2.一次对换操作后,尽量不要或者少产生额外的逆序对

eg: 共有16对 共有11

7,6,5,1,3,2,4--(47互换)-->4,6,5,1,3,2,7

一次还原处理了5,另外又多产生了(4,1)一对

在我观察的比较类排序中,快速排序体现了1却缺失了2;合并排序恰恰相反体现了2却缺失了1.

关于逆序对的相关统计算法代码参见

/*
 * Copyright (C) 2000-2007 Wang Pengcheng <wpc0000@163.com>
 * Licensed to the Wang Pengcheng under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The LGPL licenses this file to You under the GNU Lesser General Public
 * Licence, Version 2.0  (the "License"); you may not use this file except in
 * compliance with the License.  You may obtain a copy of the License at
 *
 *     
http://www.gnu.org/licenses/lgpl.txt
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 
*/

package mypackage.algorithm.unit02;

public class Inversion {
    
    
private static int tot;
    
    
/**
     *InsertionSort
     *The time limit of this sort is O(n^2)
     *<strong>O(n^2)</strong>
     *
@param  element will be sorted
     
*/

    
private static <Type extends Comparable> void insertionSort(Type element[]){
        
for(int j=1;j<element.length;j++){
            Type    key    
=    element[j];
            
int i    =    j-1;
            
            
while( (i>=0)&&(element[i].compareTo(key)>0)){
                element[i
+1]    =    element[i];
                tot
++;
                i
--;
            }

            element[i
+1]    =    key;
        }

    }

    
/**
     *Merge used in the mergeSort
     *<strong>O(n)</strong>
     *
@param element Type[] the elements to be merged
     *
@param p int the begin of the  first elements
     *
@param q int the end of the first elements
     *
@param r int the end of the second elements
     
*/

    
private static <Type extends Comparable> void merge(Type element[],int p,int q,int r){
        
int nl    =    q-p+1;
        
int nr    =    r-q;
        
        Type[] lElement,rElement;
        lElement    
=    (Type[])java.lang.reflect.Array.newInstance(element.getClass().getComponentType(),nl);
        rElement    
=    (Type[])java.lang.reflect.Array.newInstance(element.getClass().getComponentType(),nr);
                
        
for(int i=0;i<nl;i++){
            lElement[i]    
=    element[p+i];
        }

        
for(int i=0;i<nr;i++){
            rElement[i]    
=    element[q+i+1];
        }

        
        
int i=0,j=0;
        
int k    =    p;
        
while((i<nl)&&(j<nr)){
            
if(lElement[i].compareTo(rElement[j])<=0){
                element[k]    
=    lElement[i];
                i
++;
            }
else{
                element[k]    
=    rElement[j];
                j
++;
                tot
+=nl-i;
            }

            k
++;
        }

        
        
while(i<nl){
            element[k]    
=    lElement[i];
            i
++;
            k
++;
        }

        
while(j<nr){
            element[k]    
=    rElement[j];
            j
++;
            k
++;
        }

    }

    
    
/**
     *mergeSort
     *Sort the elements by the compareTo method
     *<strong>O(nlgn)</strong>
     *
@param  element Type[] that will be sorted
     *
@param p the begin of the list will be sorted
     *
@param r the end of the list will be sorted
     
*/

    
private static <Type extends Comparable> void mergeSort(Type element[],int p,int r){
        
if(p<r){
            
int q    =    (p+r)/2;
            mergeSort(element,p,q);
            mergeSort(element,q
+1,r);
            merge(element,p,q,r);
        }

    }

    
private static <Type extends Comparable> void mergeSort(Type element[]){
        mergeSort(element,
0,element.length-1);
    }

    
    
/**
     * Count the inversion number by O(nlgn)
     * 
@param <Type> inversion number type
     * 
@param element inversion number list
     * 
@return count number of inversion
     
*/

    
public static <Type extends Comparable>  int countMerge(Type element[]){
        tot
=0;
        mergeSort(element.clone());
        
return tot;
    }


    
/**
     * Count the inversion number by O(n^2)
     * 
@param <Type> inversion number type
     * 
@param element inversion number list
     * 
@return count number of inversion
     
*/

    
public static <Type extends Comparable> int countInsertion(Type element[]){
        tot
=0;
        insertionSort(element.clone());
        
return tot;
    }

    
/**
     * 
@param args
     
*/

    
public static void main(String[] args) {
        Integer[] a 
= {4,6,5,1,3,2,7};
        System.out.println(Inversion.countMerge(a));
    }


}



最后让我们牢记:

摘《李开复:算法的力量算法并不局限于计算机和网络

举一个计算机领域外的例子:在高能物理研究方面,很多实验每秒钟都能几个TB的数据量。但因为处理能力和存储能力的不足,科学家不得不把绝大部分未经处理 的数据丢弃掉。可大家要知道,新元素的信息很有可能就藏在我们来不及处理的数据里面。同样的,在其他任何领域里,算法可以改变人类的生活。例如人类基因的 研究,就可能因为算法而发明新的医疗方式。在国家安全领域,有效的算法可能避免下一个911的发生。在气象方面,算法可以更好地预测未来天灾的发生,以拯 救生命。

所以,如果你把计算机的发展放到应用和数据飞速增长的大环境下,你一定会发现;算法的重要性不是在日益减小,而是在日益加强。



谢谢大家。

参考书籍

Inroduction to AlgorithmsThomas H.Cormen,Charles E.Leiserson,Ronald L. Rivest,Clifford Stein.机械工业出版社

《数据结构》殷人昆,陶永雷,谢若阳,盛绚华 清华大学出版社

《算法艺术与信息学竞赛》刘汝佳,黄亮 清华大学出版社

《李开复:算法的力量》http://www.ieee.org.cn/dispbbs.asp?boardID=60&ID=31651

 
  • 本文已收录于以下专栏:

相关文章推荐

【初探】排序算法

日期:20160626 作者:i.sshe https://github.com/isshe 【初探】排序算法 1. 基础问题 1.1 各个常见排序算法的思想及关键代码? 2. 基础问题解答 2....

初探几种排序算法

多种排序算法的总结(不包括复杂度的详细推算) 稳定排序与不稳定排序    稳定排序:相同元素在排序中的相对位置不改变。    不稳定排序:相同元素在排序中的相对位置改变。 内部排序与外部排序: 内部排...

排序算法综合实验

  • 2017年11月15日 21:14
  • 79KB
  • 下载

java排序算法总结

  • 2016年07月25日 17:35
  • 8KB
  • 下载

算法初探——选择排序

选择排序(Selection Sort)就是通过n-1次关键字之间的比较,从n-i+1个记录中选择出最小的记录,并和第i个记录做交换。 简单来说就是 ①从第i个记录向后找一个最小的值 ②将找到的...

内部排序算法

  • 2017年05月10日 08:57
  • 239KB
  • 下载

Java各种排序算法

  • 2015年12月24日 14:41
  • 106KB
  • 下载

算法初探——快速排序

快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分...

排序算法总结 实现 Demo (Java)

  • 2016年05月19日 17:53
  • 102KB
  • 下载

堆排序算法

  • 2017年08月17日 20:57
  • 1KB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:算法初探 之 排序算法
举报原因:
原因补充:

(最多只允许输入30个字)