11.1_.jpg

 前言:本期穿插讲解一些《算法与数据结构系列》(Algorithms and Data Structures,题中简称AD),此篇为第一期——排序算法。
 
 
 
插入排序

插入排序法的思想是从前往后,先排序前两个元素,把第三个元素插入其中,然后让前三个已经排好序,再把第四个插入前三个中,排好序...以此类推,直到最后一个插入为止,这样便完成了插入排序。该算法的复杂度为n^2。

public class InsertSort {

    public static void main(String[] args)

    {

        //数组声明

        int[] a = {5,1,3,0,1,4,-1};

        int L = a.length;

        //首先确保前两个元素是升序的

        if(a[1]<a[0])

        {

            int tmp=a[0];

            a[0]=a[1];

            a[1]=tmp;

        }

        //其次进行插入排序

        for(int i=2;i<L;i++)

        {

            //如果待插入的数小于或等于已排序的表头

            if(a[i]<=a[0])

            {

                int tmp=a[i];

                for(int j=i;j>0;j--)

                {

                    a[j]=a[j-1];

                }

                a[0]=tmp;

            }

            //如果待插入的数大于或等于已排序的表尾

            else if(a[i]>=a[i-1])

                continue;

            //如果待插入的数介于已排序的表中某两个数中间

            else

            {

                for(int k=0;k<i-1;k++)

                {

                    if(a[i]>=a[k]&&a[i]<=a[k+1])

                    {

                        int tmp1=a[i];

                        for(int m=i;m>k+1;m--)

                        {

                            a[m]=a[m-1];

                        }

                        a[k+1]=tmp1;

                    }

                }

            }

        }

        for(int element:a)

            System.out.println(element+" ");

    }

}




堆排序

堆排序的思想是,首先需要构造一个堆的数据结构,我们说程序=数据结构+设计模式,因此好的数据结构对程序具有至关重要的作用,堆是一种完全二叉树,父节点大于子节点,依据根节点是最大值这一重要特性,然后依次从根节点取出元素,使得堆依次重构,最后,即可完成排序。该算法复杂度为nlogn。

import java.util.*;
 

/*

 * 本程序是进行堆排序

 * 首先需要明白堆是一种特殊的完全二叉树,并且它的孩子结点永远小于父母结点

 */

 

public class HeapSort<E extends Comparable> {

 

    public static void main(String[] args) {

        //声明一个列表,存储数据

         Integer[]list={1,0,-2,3,5,1,2,10,11,21,3,2};

         heapsort(list);

          

         for(Integer element:list)

             System.out.println(element);

    }

     

    /*

     * 首先编写堆类

     * 堆类用线性列表来存储数据

     * 

     *   */

    private ArrayList<E> list=new ArrayList<E>();

    public HeapSort(E[] objects)

    {

        for(int i=0;i<objects.length;i++)

            add(objects[i]);

    }

    

    public HeapSort() {

   

    }

     

    public void add(E object)

    {

        list.add(object);

        int currentindex=list.size()-1;

         

        while(currentindex>0)

        {

            int parentindex=(currentindex-1)/2;

            /*

             * 如果父类结点小于子类结点,则将它们交换值

             * 这个过程一直持续到父类结点不再小于子类结点为止

             */

            if(list.get(parentindex).compareTo(list.get(currentindex))<0)

            {

                E temp=list.get(currentindex);

                list.set(currentindex, list.get(parentindex));

                list.set(parentindex, temp);

            }

            else

                break;

            currentindex=parentindex;//把新加的数的下标更新

        }

    }

     

    @SuppressWarnings("unchecked")

    public E remove()

    {

        if(list.size()==0)

            return null;

        /*

         * 基本思想是

         * remove()方法只能移除根处的元素

         * 然后重新组建堆

         * 首先把最后一个元素移到根处

         * 然后将根与它的结点进行比较,若小于其子结点,则交换下移即可

         * 直到下标最大为止

         */

        E removedObject=list.get(0);

        list.set(0, list.get(list.size()-1));

        list.remove(list.size()-1);

         

        int currentindex=0;

        while(currentindex<list.size())

        {

            int leftchildindex=2*currentindex+1;//左孩子

            int rightchildindex=2*currentindex+2;//右孩子

             

            if(leftchildindex>=list.size())

                break;

            /*

             * 下面语句确定左边子结点与右边子结点的大小

             * 以便将父结点与最大的子结点进行比较

             * 进而确定是否执行交换动作

             */

            int maxindex=leftchildindex;

            if(rightchildindex<list.size())

            {

                if(list.get(maxindex).compareTo(list.get(rightchildindex))<0)

                    maxindex=rightchildindex;

            }

             

            /*

             * 下面开始执行交换动作

             * 若父结点比最大的子结点要小

             * 则交换它们

             */

            if(list.get(currentindex).compareTo(list.get(maxindex))<0)

            {

                E temp=list.get(currentindex);

                list.set(currentindex, list.get(maxindex));

                list.set(maxindex, temp);

                currentindex=maxindex;

            }

            /*

             * 直到父结点不再小于最大的子结点为止

             */

            else

                break;  

        }

        return removedObject;

    }

     

    public static <E extends Comparable>void heapsort(E[]list)

    {

        HeapSort<E> heap=new HeapSort<E>();     

        for(int i=0;i<list.length;i++)

            heap.add(list[i]);      

        for(int i=list.length-1;i>=0;i--)

            list[i]=heap.remove();

         

    }

}




归并排序

归并排序的思想源自分而治之,即把一个大任务分割成两个小任务,再递归分割,直到不能分割为止。对于一个数组,我们可以将其分成两部分,再将各小部分分别分成两部分,最后使得数组分成N份,N为元素总数,再两两按大小合并,四四按大小合并...直到最后合并完整为止,即可完成排序。该算法复杂度为nlogn。



public class MergeSort {


    //归并排序,首选采用分而治之的思想,运用递归方法,将数组分组两半

    public static void guibing(int[] list)

    {

        int L=list.length;

        //将数组递归分成两部分

        if(L>1)

        {

            //第一部分为前一半元素

            int[] firstHalf=new int[L/2];

            //调用arrayCopy方法,将list中的前一半元素复制到firstHalf之中

            System.arraycopy(list, 0, firstHalf, 0, L/2);

            //递归:将这个子数组再分成两半

            guibing(firstHalf);

            //第二部分为后一半元素(不一定与第一部分元素个数相同)

            int[] secondHalf=new int[L-L/2];

            //调用arrayCopy方法,将list中的后一半元素复制到secondHalf之中

            System.arraycopy(list, L/2, secondHalf, 0, secondHalf.length);

            //递归:将这个子数组再分成两半

            guibing(secondHalf);

            //一旦所有的递归拆分都已经结束,那么进行归并

            int[]temp=merge(firstHalf,secondHalf);

            //将temp数组的所有元素复制到list之中

            //参数的含义:源数组,首索引,目标数组,首索引,复制长度

            System.arraycopy(temp, 0, list, 0, temp.length);

        }

    }

    //归并两个数组

    //因为两个数组已经是排序的,所以只需依次比较两个元素的对应值即可,"谁小就谁上"

    private static int[] merge(int[] list1,int[] list2)

    {

        //声明最终数组及长度

        int[] tmp=new int[list1.length+list2.length];

        //设置索引量初始化

        int current1=0,current2=0,current3=0;

        //当两个子数组均没有结束,需要比较它们的值

        while(current1<list1.length&&current2<list2.length)

        {

            if(list1[current1]<list2[current2])

                tmp[current3++]=list1[current1++];

            else

                tmp[current3++]=list2[current2++];

        }

        //如果只剩下第一个子数组了,就直接把剩下的元素依次放入最终数组之中

        while(current1<list1.length)

            tmp[current3++]=list1[current1++];

        //如果只剩下第二个子数组了,就直接把剩下的元素依次放入最终数组之中

        while(current2<list2.length)

            tmp[current3++]=list2[current2++];

        //至此,合并结束,返回最终数组

        return tmp;

    }

    public static void main(String[] args) {

        //首先声明数组

        int[]a={-5,1,3,2,1,0,2,-2,0};

        int L=a.length;

        //归并排序

        guibing(a);

        //输出结果

        for(int element:a)

            System.out.println(element);
     
    }

}




快速排序

快速排序算法的思想其实也是来自分治法,不过除了这个之外,还是用了定与不定的思想,即我在分割任务的同时,每一步都可以寻找一个定的元素,即找主元的位置。主元的位置一旦确定,即不会改变,而将N个元素两两分割直到不能分割为止,运用递归的思想,在每一小块中都寻找主元的位置。最终完成了快速排序。该算法复杂度为nlogn。

/*

 * @快速排序法

 */

public class QuickSort {

 

    //快速排序主程序

    public static void sort(int[] arr)

    {

         quickSort(arr,0,arr.length-1);

    }

    //快速排序法递归,表明要排序的数组,以及起始索引

    private static void quickSort(int[]arr,int first,int last)

    {

        //只要最后一个大于第一个的索引

        if(last>first)

        {

            //将数组分为两部分,并且选择第一个为主元

            int pivotIndex=partition(arr,first,last);

            //对主元分开的两个子数组进行排序

            quickSort(arr,first,pivotIndex-1);

            quickSort(arr,pivotIndex+1,last);

        }

    }

    //寻找主元并且划分数组的部分

    public static int partition(int[] list,int first,int last)//划分数组为两部分

    {

        //选择第一个为主元

        int pivot=list[first];

        //后面将数组划分为大于主元和小于主元的两大块

        int low=first+1;

        int high=last;

         

        //如果high大于low

        while(high>low)

        {

            //如果low的元素小于pivot就继续推进

            while(low<=high&&list[low]<=pivot)

                low++;

            //如果high的元素大于pivot就继续后退

            while(low<=high&&list[high]>pivot)

                high--;

            //否则,说明出现了相反情况,则交换两个元素

            if(high>low)//当list[high]<list[low]时,就交换二者

            {

                int temp=list[high];

                list[high]=list[low];

                list[low]=temp;

            }

        }

         

        while(high>first&&list[high]>=pivot)

            high--;

        //如果一开始选的主元比最后一个high元素要大

        if(pivot>list[high])

        {

            list[first]=list[high];

            list[high]=pivot;

            return high;

        }

        else

            return first;

    }

    public static void main(String[] args) {

        //声明数组

        int[]arr={1,2,3,0,1,5,-1,2};

        //调用快速排序算法

        sort(arr);

        //输出结果

        for(int element:arr)

            System.out.println(element);

 

    }

}




冒泡排序





冒泡排序估计是最为熟悉的,也是最好写的,它的思想是每一次遍历都会两两比较,比如说从最后一个元素往前遍历的话,遇到比自己大的就交换过来,于是,这样最后的话,相当于小的元素都浮上来了,形象地称为冒泡法,一共最多需遍历n趟,当然如果某一次遍历发现什么动作也没有,说明排序已经完成了。该算法复杂度为n^2。

public class BubbleSort {

     

    public static void main(String[] args)

    {

        //声明数组

        int[]a={-5,1,3,2,1,0,2,-2};

        int L=a.length;

        //声明临时变量

        int tmp;

        int Flag=0;

        //若某次遍历中Flag的值保持为1,则说明排序已经完成,可以退出了

        while(Flag==0)

         {

             Flag=1;

             for(int i=0;i<L-1;i++)

             {

                 //交换相邻元素

                 if(a[i]>a[i+1])

                 {

                     tmp=a[i];

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

                     a[i+1]=tmp;

                     Flag=0;

                 }

             }

         }

        //输出结果

        for(int element:a)

            System.out.println(element);

    }

}




选择排序




选择排序算法的思想也很简单,先假设第一个数为最小数,作为基准,然后依次向后轮询,遇到比最小的数还要小,就替换掉,一趟过后,第一个数就确定了,接下来以此类推,将第二个数确定下来......该算法复杂度为n^2.

public class SelectSort {

 

    public static void main(String[] args)

    {

        //首先声明数组

        int a[]={-5,1,3,2,1,0,2,-2};

        int L=a.length;

        //找到最小的数,依次放到表头...

        for(int i=0;i<L;i++)

         {

            //找出最小的数

             for(int j=i+1;j<L;j++)

             {

                 if(a[j]<a[i])

                 {

                     int T=a[j];

                     a[j]=a[i];

                     a[i]=T;

                 }

             }

         }

        //输出结果

        for(int element:a)

            System.out.println(element);

    }

}



最后分享摘自余秋雨《千年一叹》一书中的名句:

金字塔至今不肯坦示为什么要如此永久,却透露了永久是什么。



 
 
 
来源:张泽旺 深度学习每日摘要
智造家