算法导论学习笔记(三):排序算法

转载请注明原作者 @yoshino
更新不及时,有空慢慢写吧,原文是写在Bear上粘贴来的,可能格式有点乱。
具体的工程在我的github上。
主要思想方法参照了该博客:面试中的排序算法总结, 作者:Pickle


本文都是默认升序排序
[TOC]

冒泡排序(Bubble Sort)

  • 非常容易的算法,就像冒泡泡一样,每个数组里的元素冒到它合适的位置,保证它左边的数比它小,右边的数比他大。
  • 很容易验证时间的复杂度是O(n2),每排序一个数要遍历一次数组,排序n个数要遍历n*n
  • 代码实现:
public void BubbleSort(int[] num) {
        for (int i = 0; i < num.length; i++)
            for (int j = 0; j < num.length; j++) {
                if (num[i] < num[j]) {
                    int temp = num[i];
                    num[i] = num[j];
                    num[j] = temp;
                }
            }
        for (int i = 0; i < num.length; i++) {
            System.out.print(num[i] + " ");
        }
    }

快速排序(Quick Sort)

  • 实现原理也不难,也是让一个数(midValue)达到一个位置,在这个位置上保证它左边的数比它小,右边的数比他大。然后以这个数为基准,生成它左边和右边两个数组,左边的数组肯定会满足小于midValue,右边的数满足大于midValue,最后在递归这两个数组。
  • 时间复杂度是:O(n*log2n),打不了公式就不做推理了,大学算法书上基本都能找得到的。
  • 代码实现:
public void QuickSort(int[] num, int low, int high) {
        int l = low;
        int h = high;
        int povit = num[low];
        while (l < h) {
            while (l < h && num[h] >= povit)
                h--;
            if (l < h) {
                int temp = num[h];
                num[h] = num[l];
                num[l] = temp;
                l++;
            }
            while (l < h && num[l] <= povit)
                l++;

            if (l < h) {
                int temp = num[h];
                num[h] = num[l];
                num[l] = temp;
                h--;
            }
        }
        for (int i = 0; i < num.length; i++) {
            System.out.print(num[i] + " ");
        }
        System.out.print("\n");
        if (l > low) QuickSort(num, low, l - 1);
        if (h < high) QuickSort(num, l + 1, high);
    }//quick sort

插入排序 (Insert Sort)

  • 思想很简单,构建一个有序列表,把待排序的数插入到列表合适的位置上。
  • 时间复杂度为O(n^2),必须遍历待排序数组和遍历有序列表找位置
  • 代码实现:
public void InsertSort(int[] num) {
        for (int i = 1; i < num.length; i++)
            for (int j = i; j > 0; j--) {
                if (num[j] < num[j - 1]) {
                    int temp = num[j];
                    num[j] = num[j - 1];
                    num[j - 1] = temp;
                }
            }
        for (int i = 0; i < num.length; i++) {
            System.out.print(num[i] + " ");

        }
    }

选择排序(Select Sort)

  • 每次从待排序列表中选择最小的元素,将其放在列表的最后,从而获得一个新的有序列表。
  • 时间复杂度也是O(n^2),遍历一遍查找最小值和插入,使用ArrayList.add较方便。
  • 代码实现:
public void SelectSort(int[] num) {
        int[] numSorted = new int[num.length];
        int pos = 0;
        int p = 0;
        int n = num[0];
        while (pos < 10) {
            for (int i = 0; i < num.length - 1; i++) {
                if (n > num[i]) {
                    n = num[i];
                    p = i;
                }
            }
            numSorted[pos] = n;
            num[p] = 99999;
            p = 0;
            n = num[pos];
            pos++;
        }
        for (int i = 0; i < num.length; i++) {
            System.out.print(numSorted[i] + " ");

        }

    }

堆排序(HeapSort)

  • 建立二叉树方式,建立小根堆,满足其子节点均不小于父节点,那么根节点就是二叉树列表中最小的值,取出后重建小根堆即可。
  • 时间复杂度为O(N*logN),空间复杂度为O(1),由于建堆开销大,所以适合大数据量中取最小(最大)的前n项。
  • 代码实现:
public void HeapSort(int[] num) {
        if (num == null || num.length <= 1) {
            return;
        }
        buildMaxHeap(num);
        for (int i = num.length - 1; i >= 1; i--) {
            int temp = num[0];
            num[0] = num[i];
            num[i] = temp;
            maxHeap(num, i, 0);
        }
        for (int i = 0; i < num.length; i++) {
            System.out.print(num[i] + " ");
        }
    }

    private void buildMaxHeap(int[] num) {
        if (num == null || num.length <= 1) {
            return;
        }

        int half = num.length / 2;
        for (int i = half; i >= 0; i--) {
            maxHeap(num, num.length, i);
        }
    }

    private void maxHeap(int[] num, int heapSize, int index) {
        int left = index * 2 + 1;
        int right = index * 2 + 2;

        int largest = index;
        if (left < heapSize && num[left] > num[index]) {
            largest = left;
        }

        if (right < heapSize && num[right] > num[largest]) {
            largest = right;
        }

        if (index != largest) {

            int temp = num[index];
            num[index] = num[largest];
            num[largest] = temp;
            maxHeap(num, heapSize, largest);
        }
    }

希尔排序(Shell Sort)

  • 先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后取第二个增量d2
  • 时间复杂度是O(n*log2n),空间复杂度是O(1),相当于直接插入排序的高级版。
  • 代码实现:
public void ShellSort(int[] num) {
        int d = num.length;
        while (d != 1) {
            d = d / 2;
            for (int i = 0; i < num.length; i++)
                for (int j = i; j + d < num.length; j = j + d) {//分组后从i开始,每d增量拿出来一次
                    if (num[j] > num[j + d]) {
                        int temp = num[j + d];
                        num[j + d] = num[j];
                        num[j] = temp;
                    }
                }
        }
        for (int i = 0; i < num.length; i++) {
            System.out.print(num[i] + " ");
        }
    }

归并排序(Merge Sort)

  • 分治法的强力体现,先让子序列有序,然后把有序的子序列合成一个有序的列表从而完成排序归并。
  • 空间复杂度为O(nlogn),时间复杂度O(n)
  • 代码实现:
    private void MergeSort1(int[] num, int low, int high) {
        int mid = (low + high) / 2;
        if (low < high) {
            MergeSort1(num, low, mid);
            MergeSort1(num, mid + 1, high);
            merge(num, low, mid, high);
        }

    }

    private void merge(int[] num, int low, int mid, int high) {
        int[] temp = new int[high - low + 1];
        int i = low;
        int j = mid + 1;
        int k = 0;
        while (i <= mid && j <= high) {
            if (num[i] < num[j]) {
                temp[k++] = num[i++];
            } else {
                temp[k++] = num[j++];
            }
        }
        while (i <= mid) {
            temp[k++] = num[i++];
        }
        while (j <= high) {
            temp[k++] = num[j++];
        }
        for (int k2 = 0; k2 < temp.length; k2++) {
            num[k2 + low] = temp[k2];
        }
    }

计数排序(Count Sort)

  • 一种稳定的线性时间排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。
  • 计数排序是典型的以空间换时间的方法来加快排序进度,所以时间复杂度是O(n),空间复杂度是O(n+k),k为辅助数组大小,一般为数组的最大值与最小值之差+1
  • 算法的步骤如下:

1.找出待排序的数组中最大和最小的元素
2.统计数组中每个值为i的元素出现的次数,存入数组C的第i项
3.对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
4.反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

  • 代码实现:
public void CountSort(int[] num) {
        int max, min;
        max = min = num[0];
        int[] numSorted = new int[num.length];
        for (int i = 0; i < num.length; i++) {
            if (max < num[i]) {
                max = num[i];
            }
            if (min > num[i]) {
                min = num[i];
            }
        }//找出数组中最大最小值,便于建立辅助数组
        int d = max - min + 1;
        int[] c = new int[d];//辅助数组
        for (int i = 0; i < num.length; ++i) {
            c[num[i] - min] = c[num[i] - min] + 1;
        }
        for (int i = 1; i < c.length; ++i) {
            c[i] = c[i] + c[i - 1];
        }
        for (int i = num.length - 1; i >= 0; --i) {
            c[num[i] - min]=c[num[i] - min]-1;
            numSorted[c[num[i] - min]] = num[i];//按存取的方式取出c的元素
        }
        for (int i = 0; i < num.length; i++) {
            System.out.print(numSorted[i] + " ");
        }
    }

桶排序(Bucket Sort)

  • 桶排序类似于计数排序,假设有一组长度为N的待排关键字序列K[1....n]。首先将这个序列划分成M个的子区间(桶) 。然后基于某种映射函数 ,将待排序列的关键字k映射到第i个桶中(即桶数组B的下标 i) ,那么该关键字k就作为B[i]中的元素(每个桶B[i]都是一组大小为N/M的序列)。接着对每个桶B[i]中的所有元素进行比较排序(可以使用快排)。然后依次枚举输出B[0]....B[M]中的全部内容即是一个有序序列。
  • 桶排序的关键是找到这个映射函数,使得B(i)中的最小数据都要大于B(i-1)中最大数据。很显然,映射函数的确定与数据本身的特点有很大的关系。
  • 一般来说正整数排序中,我们常取用的映射函数是:f(k)=k/10
  • 对于N个待排数据,M个桶,桶排序的平均时间复杂度为线性的O(N+C),其中C=N*(logN-logM)。如果相对于同样的N,桶数量M越大,其效率越高,最好的时间复杂度达到O(N)(此时 N=M )。桶排序的空间复杂度 为O(N+M)桶排序是稳定的。
public void BucketSort(int[] num) {//映射函数为f(x)=k/10
        int buckets = 10;//建造十个桶
        ArrayList> bucketNum = new ArrayList>();//建立一个桶的索引
        for (int i = 0; i < buckets; i++) {
            bucketNum.add(new ArrayList());
        }//初始化十个桶的索引
        for (int i = 0; i < num.length; i++) {
            int pos = num[i] / 10;//通过映射函数选择放在哪个桶
            bucketNum.get(pos).add(num[i]);
        }
        for (int i = 0; i < buckets; i++) {
            if (bucketNum.get(i).isEmpty()) {
                continue;
            } else {
                bucketNum.get(i).sort(Integer::compareTo);//这里使用了Arraylist自带的sort方式,可以自己写排序算法
            }

        }
        for (int i = 0; i < buckets; i++) {
            if (bucketNum.get(i).isEmpty()) {
                continue;
            } else {
                for (int j = 0; j < bucketNum.get(i).size(); j++) {
                    System.out.print(bucketNum.get(i).get(j) + " ");
                }
            }
        }
        System.out.println();
    }

基数排序

  • 基数排序不需要进行记录关键字之间的比较。基数排序是一种借助多关键字排序思想对单逻辑关键字进行排序的方法。所谓的多关键字排序就是有多个优先级不同的关键字。比如说成绩的排序,如果两个人总分相同,则语文高的排在前面,语文成绩也相同则数学高的排在前面。。。如果对数字进行排序,那么个位、十位、百位就是不同优先级的关键字,如果要进行升序排序,那么个位、十位、百位优先级一次增加。基数排序是通过多次的收分配和收集来实现的,关键字优先级低的先进行分配和收集。

你可能感兴趣的:(算法导论学习笔记(三):排序算法)