面试---算法

java实现八大排序算法

 

Arrays.sort() 采用了2种排序算法 -- 基本类型数据使用快速排序法,对象数组使用归并排序.

java的Collections.sort算法调用的是归并排序,它是稳定排序

方法一:直接插入

1.基本思路:

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

2.代码实现:

(1)首先设定插入次数,即循环次数,for(int i=1;i<length;i++),从第二个数字开始插入,排序好。再依次插入第三个。。。

(2)设定插入数和得到已经排好序列的最后一个数的位数。temp和j=i-1。

(3)从最后一个数开始向前循环,如果插入数小于当前数,就将当前数向后移动一位。

(4)将当前数放置到空着的位置,即j+1。

复制代码
    public static void insertSort(int[] data){
        int temp;
        for(int i=1;i<data.length;i++){//取第i个数,插入前边的有序的序列
            temp = data[i];
            int j;
            for(j =i-1;j>=0;j--){//从第i-1的位置上开始比较
                if(data[j]>temp){//若前面的数大,则往后挪一位
                    data[j+1] = data[j];
                }else {
                    break;//否则,说明要插入的数比较大
                }
            }
            data[j+1] = temp;//找到这个位置,插入数据
        }
    }
复制代码

3.时间复杂度和空间复杂度:

直接插入排序的平均复杂度为O(n²),最坏时间复杂度:O(n²),空间复杂度:O(1),没有分配内存。

方法二:希尔排序

1.定义:

针对直接插入排序的下效率问题,有人对次进行了改进与升级,这就是现在的希尔排序。希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法。

2.基本思路:

数的个数为length,i=length/2,将下标差值为i的数分为一组,构成有序序列。

再取i=i/2 ,将下标差值为i的数分为一组,构成有序序列。

重复第二步,直到k=1执行简单插入排序。 

思路

(1)希尔排序(shell sort)这个排序方法又称为缩小增量排序,是1959年D·L·Shell提出来的。该方法的基本思想是:设待排序元素序列有n个元素,首先取一个整数increment(小于n)作为间隔将全部元素分为increment个子序列,所有距离为increment的元素放在同一个子序列中,在每一个子序列中分别实行直接插入排序。然后缩小间隔increment,重复上述子序列划分和排序工作。直到最后取increment=1,将所有元素放在同一个子序列中排序为止。
(2)由于开始时,increment的取值较大,每个子序列中的元素较少,排序速度较快,到排序后期increment取值逐渐变小,子序列中元素个数逐渐增多,但由于前面工作的基础,大多数元素已经基本有序,所以排序速度仍然很快。
(3)希尔排序举例:

3.代码实现:

(1)首先确定每一组序列的下标的间隔,循环每次需要的间隔:int i = length/2; i >0 ; i /= 2

(2)然后将每一组序列中元素进行插入排序,第二组第一个插入的数字是第一组第一个插入数字之后的那个数组,从i之后每个数字都要进行插入排序,就是插入的序列是各自不同的序列,不是一个一个子序列循环,而是在一个循环中for (int j=i;j<length;j++)完成所有子序列的插入排序。

(3)直到i=0为止。

复制代码
    public static void shellSort(int[] array){
        int length = array.length;
        for (int i = length/2; i >0 ; i /= 2) {//序列的间隔,一直到间隔为一,这时候就只有一个子序列
            for (int j=i;j<length;j++){//从i之后每个数字都要进行插入排序,就是插入的序列是各自不同的序列
                int temp = array[j];//里面就是直接插入算法
                int k;
                for(k = j-i; k>=0;k -= i){//实现各个数字插入排序到不同的序列中,直到间隔为1的时候,只有一个序列,就是完全的一个直接插入排序
                    if(temp< array[k]){
                        array[k+i] = array[k];
                    }else{
                        break;
                    }

                }
                array[k+i] = temp;//把数字插入到位置上
            }
        }
        System.out.println(Arrays.toString(array));
    }
复制代码

4.时间复杂度和空间复杂度:

希尔排序的平均时间复杂度为O(n²),空间复杂度O(1) 。

方法三:简单选择

1.基本思路:

基本原理如下:对于给定的一组记录,经过第一轮比较后得到最小的记录,然后将该记录的位置与第一个记录的位置交换;接着对不包括第一个记录以外的其他记录进行第二次比较,得到最小记录并与第二个位置记录交换;重复该过程,知道进行比较的记录只剩下一个为止。

2.代码实现:

(1)确定要插入最小值的位置,从0开始到最后int i = 0; i <len ; i++

(2)将每次开始位置上的数字暂定为最小值min,从开始数字之后一个个和min比较,再把最小值存放到min

(3)将最小值所在位置上的数字和开始位置上的数字交换

复制代码
    public static void selectSort(int[] array){
        int len = array.length;
        for (int i = 0; i <len ; i++) {//确定每次开始的位置

            int min = array[i];//设定开始数字为最小的值最小值
            int flag = i;
            for(int j=i+1;j<len;j++){//把最小值存放到min,从开始数字向后一个个和min比较,再把最小值存放到min
                if(min>array[j]){
                    min = array[j];
                    flag = j;
                }
            }
            if(flag != i){
                array[flag] = array[i];
                array[i] = min;
            }


        }
        
    }
复制代码

3.时间复杂度:

简单选择排序时间复杂度:O(n^2)

方法四:堆排序

1.基本思路:

(1)若array[0,...,n-1]表示一颗完全二叉树的顺序存储模式,则双亲节点指针和孩子结点指针之间的内在关系如下:

  任意一节点指针 i:父节点:i==0 ? null : (i-1)/2

            左孩子:2*i + 1

            右孩子:2*i + 2

(2)堆的定义:n个关键字序列array[0,...,n-1],当且仅当满足下列要求:(0 <= i <= (n-1)/2)

      ① array[i] <= array[2*i + 1] 且 array[i] <= array[2*i + 2]; 称为小根堆;

      ② array[i] >= array[2*i + 1] 且 array[i] >= array[2*i + 2]; 称为大根堆;

(3)建立大根堆:

  n个节点的完全二叉树array[0,...,n-1],最后一个节点n-1是第(n-1-1)/2个节点的孩子。对第(n-1-1)/2个节点为根的子树调整,使该子树称为堆。

  对于大根堆,调整方法为:若【根节点的关键字】小于【左右子女中关键字较大者】,则交换。

  之后向前依次对各节点((n-2)/2 - 1)~ 0为根的子树进行调整,看该节点值是否大于其左右子节点的值,若不是,将左右子节点中较大值与之交换,交换后可能会破坏下一级堆,于是继续采用上述方法构建下一级的堆,直到以该节点为根的子树构成堆为止。

  反复利用上述调整堆的方法建堆,直到根节点。

(4)堆排序:(大根堆)

  ①将存放在array[0,...,n-1]中的n个元素建成初始堆;

  ②将堆顶元素与堆底元素进行交换,则序列的最大值即已放到正确的位置;

  ③将数组中array[0,...,n-1]前n-1个元素再次形成大根堆,再重复第②③步,直到堆中仅剩下一个元素为止。

2.代码实现:

复制代码
    public static int[] buildMaxHeap(int[] array,int length){//截取数组从0到length,构建大根堆

        for (int i = (length-2)/2; i >=0 ; i--) {//从最后一个有子结点的结点开始调整,(减一)一直调节到根节点

            adjustDownToUp(array,i,length);

        }
        return array;
    }

    private static void adjustDownToUp(int[] array, int i, int length) {//i要调整的结点
        int temp = array[i];
        //int j = i*2+1;//取得左孩子
        for (int j=i*2+1;j<length;j=j*2+1){//从该节点一直调节到叶子结点,因为上层调节改变之后,会影响下层结构,所以一直循环到叶子结点。
            if(j+1<length && array[j]<array[j+1]){//有又孩子,且右孩子数值更大
                j++;//取更大结点的索引
            }
            if(temp<array[j]){//根节点比最大的值小
                array[i] = array[j];//调整根节点的值为最大值
                i=j;//把空余的位置给i
                //array[j] = temp;
            }
        }
        array[i] = temp;//找到最后空余的位置,填上向下调整的值

    }
    public static void heapSort(int[] array){
        int len = array.length;
        int temp;
        for (int i = len; i >0; i--) {
            array=buildMaxHeap(array,i);
            temp = array[0];
            array[0] = array[i-1];
            array[i-1]=temp;
        }
        System.out.println(Arrays.toString(array));

    }
复制代码

3.时间复杂度:

时间复杂度:建堆:o(n),每次调整o(log n),故最好、最坏、平均情况下:o(n*logn);

方法五:冒泡排序

1.基本思路:

一次冒泡将序列中从头到尾所有元素两两比较,将最大的放在最后面。

将剩余序列中所有元素再次两两比较,将最大的放在最后面。

重复第二步,直到只剩下一个数。

2.代码实现:

1
2
3
4
5
6
7
8
9
10
11
public static void bubbleSort(int[] array){
    for (int i = 0; i <array.length ; i++) {//第i冒泡,一次冒泡,会确定一个最大值
        for (int j = 0; j <array.length-i-1 ; j++) {//从头一直到已经确定的位置前,两两比较
            int temp = array[j];
            if(array[j]>array[j+1]){
                array[j]=array[j+1];
                array[j+1]=temp;
            }
        }
    }
}

3.时间复杂度:

冒泡排序的时间复杂度为O(n^2),空间复杂度为O(1),它是一种稳定的排序算法。

方法六:快排

1.基本思路:

快速排序使用分治策略来把一个序列(list)分为两个子序列(sub-lists)。步骤为:

  1. 从数列中挑出一个元素,称为"基准"(pivot)。
  2. 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
  3. 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。

递归到最底部时,数列的大小是零或一,也就是已经排序好了。这个算法一定会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

2.代码实现:

复制代码
public static void quickSprt(int[] array,int low,int high){
        if(low>=high) return;
        int left = low;
        int right = high;
        int pivot = array[left];//设立基准点
        while (left<right){
            while (left<right && array[right]>pivot)//从右向左,大数位置不变
                right--;
            array[left] = array[right];//把小数移到左边

            while (left < right && array[left]<pivot) //从左向右,小数位置不变
                left++;
            array[right] = array[left];//把大数移到右边

        }

        array[left]=pivot;
        quickSprt(array,low,left-1);
        quickSprt(array,left+1,high);

    }
复制代码

3.时间复杂度:

虽然 快排的时间复杂度达到了 O(n²),但是在大多数情况下都比平均时间复杂度为 O(n logn) 的排序算法表现要更好。

方法七:归并排序

1.基本思路:

对于给定的一组记录,利用递归与分治技术将数据序列划分成为越来越小的半子表(直到剩余一个数字),在对半子表排序,最后再用递归方法将排好序的半子表合并成为越来越大的有序序列。 

(1)申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列 

(2)设定两个指针,最初位置分别为两个已经排序序列的起始位置 

(3)比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置 

(4)重复步骤3直到某一指针达到序列尾 

(5)将另一序列剩下的所有元素直接复制到合并序列尾

2.代码实现:

复制代码
    //归并排序
    public static int[] mergeSort(int[] array,int low,int high){
        if(low<high){
            int mid = (low+high)/2;
            mergeSort(array,low,mid);
            mergeSort(array,mid+1,high);
            merge(array,low,mid,high);//归并
        }
        return array;
    }

    private static void merge(int[] array, int low, int mid, int high) {
        int i = low;//指针,前一个序列的头指针
        int j = mid+1;//指针,后一个序列的头指针
        int[] temp = new int[high-low+1];
        int k=0;
        while (i<=mid && j<= high){
            if (array[i]<array[j]){//从头比较两个序列,小的放入临时数组temp
                temp[k++] = array[i++];//前一个序列指针后移一位
            }else {
                temp[k++] = array[j++];//后一个序列指针后移一位
            }

        }
        //最后只会剩下一组序列
        while (i<=mid){
            temp[k++] = array[i++];//把前一个指针剩余的数字放入临时数组
        }
        while (j<=high){
            temp[k++] = array[j++];//把后一个指针剩余的数字放入临时数组
        }

        for (int m = 0; m <high-low+1 ; m++) {
            array[low+m] = temp[m];
        }

    }
复制代码

 

3.时间复杂度:

时间复杂度:O(nlog2n)

方法八:基数排序

1.基本思路:

(1)按照所有的数的个位数,在0---9 的基数序列中,把各个数字排入,每一个数字基数上也是一个序列。

(2)将新序列取出按基数顺序取出,形成新数组。

(3)再按照十位数字进行排序,插入到基数序列,循环(1)(2)

2.代码实现:

复制代码
public static void baseSort(int[] array){
        ArrayList<ArrayList<Integer>> queue = new ArrayList<ArrayList<Integer>>();
        for (int i = 0; i <10 ; i++) {
            ArrayList<Integer> queue2 = new ArrayList<Integer>();
            queue.add(queue2);//创建一个基数从0---9 每个数字上都是一个list
        }
        //找到最大值,并判断最大值是几位数
        int max = array[0];
        for (int i = 1; i < array.length; i++) {
            if(max<array[i]){
                max = array[i];
            }
        }
        int time = 0;
        while(max>0){
            max /= 10;
            time++;
        }
        for (int i = 0; i < time; i++) {//循环每一个位数(个位、十位、百位)
            for (int j = 0; j <array.length ; j++) {//循环数组,取每一个值
                int x = array[j] % (int)Math.pow(10,i+1) / (int)Math.pow(10,i);
                ArrayList<Integer> queue3 = queue.get(x);;
                queue3.add(array[j]);
                queue.set(x,queue3);
            }

            int count = 0;
            for (int k = 0; k < 10; k++) {
                while (queue.get(k).size() > 0) {
                    ArrayList<Integer> queue4 = queue.get(k);
                    array[count] = queue4.get(0);
                    queue4.remove(0);
                    count++;
                }
            }

        }

    }
复制代码

3.时间复杂度:

时间复杂度:O(nlog2n)

总结:

排序算法 思路 排序算法 最好时间复杂度 平均时间复杂度 最坏时间复杂度

空间复杂度

(辅助存储)

是否稳定
插入排序 将一个数字插入已经排列好的序列中 直接插入法 O(N) O(N2) O(N2) O(1) 稳定
 希尔排序 O(N)  O(N1.3) O(N2  O(1)  不稳定
选择排序 每一次循环都是选择出最小(最大)的数字放到数组最前面(最后面)的位置  简单选择  O(N)  O(N2) O(N2) O(1) 不稳定
堆排序   O(N*log2N) O(N*log2N)   O(N*log2N) O(1) 不稳定
交换排序

(冒泡)两两交换,大的后移,再次两两交换  

(快速)和基准交换,比基准大排右边,比基准小排左边

冒泡排序 O(N)   O(N2) O(N2)  O(1) 稳定
快速排序  O(N*log2N) O(N*log2N)    O(N2 O(log2n)~O(n)  不稳定
归并排序  O(N*log2N)    O(N*log2N) O(N*log2N)   O(n)  稳定
基数排序  O(d(r+n))   O(d(r+n)) O(d(r+n))  O(rd+n)  稳定
 
分类: 数据结构
posted @ 2021-02-26 17:17  abcdefghijklmnop  阅读(51)  评论(0编辑  收藏  举报