算法与数据结构之排序

排序根据算法的复杂度(平均复杂度)可以大致分为一下三类:

  1. O(n^2):冒泡排序(Bubble Sort),插入排序(Inserttion Sort),选择排序(Selection Sort)
  2. O(nlogn):归并排序(Merge Sort),堆排序(Heap Sort),快速排序(Quick Sort)
  3.  O(n):计数排序(Counting Sort),基数排序(Radix Sort)
排序根据算法的稳定性(对于相同的元素是否能够在排序前后保证相对顺序),可以两类:
  1. 稳定排序:
  2. 不稳定排序:

1、冒泡排序

冒泡排序是模拟气泡从水底部上浮的过程,对于一个待排序的序列而言,较小的数会一直向上”浮“,直至所有的较小的数都在较大数的上面(左边),就完成了排序。以下为Java实现, 可以看出冒泡排序是稳定排序。
    void bubbleSort(int[] nums) {
        for(int i = 0; i < nums.length; i++) {
            for(int j = nums.length - 2; j >= 0; j--) {
                if(nums[j] > nums[j+1]) {
                    int tmp = nums[j+1];
                    nums[j+1] = nums[j];
                    nums[j] = tmp;
                }
            }
        }
    }

2、插入排序

插入排序主要是通过将当前元素插入到前面已经排好序的序列当中来完成排序。以下为Java实现。插入排序有一种双排的实现方式,每次考虑两个元素a, b(a>b),先排好a,接着从a的位置排b,可以减少比较次数。
    void insertionSort(int[] nums) {
        for(int i = 1; i < nums.length; i++) {
            for(int j = i; j > 0; j--) {
                if(nums[j-1] > nums[j]) {
                    int tmp = nums[j];
                    nums[j] = nums[j-1];
                    nums[j-1] = tmp;
                }
            }
        }
    }

3、选择排序

每次选择未排序序列的最小值,追加到已排好序列的末端。一下为Java实现,选择排序为不稳定排序,如3,3,1。
   void selectionSort(int[] nums) {
        for(int i = 0; i < nums.length; i++) {
            int min = nums[i], pos = i;
            for(int j = i+1; j < nums.length; j++) {
                if(nums[j] < min) {
                    min = nums[j];  pos = j;
                }
            }
            if(pos != i) {
                int tmp = nums[i];
                nums[i] = nums[pos];
                nums[pos] = tmp;
            }
        }
    }

4、归并排序

归并排序,从名字上就可以大致看出来有归一合并的过程。将n长度的排序问题分解成长度相等(n/2, n-n/2)的排序子问题,之后将子问题的结果(两个排好序的子序列)合并成一个序列,完成排序。
    void merge(int[] nums, int left, int right) {
        int mid = (left + right)/2;
        int[] tmp = new int[right - left + 1];
        int cnta = left, cntb = mid+1, cnt = 0;
        while (cnta <= mid && cntb <= right) {
            if(nums[cnta] > nums[cntb])  tmp[cnt++] = nums[cntb++];
            else                         tmp[cnt++] = nums[cnta++];
        }
        while (cnta <= mid)  tmp[cnt++] = nums[cnta++];
        while (cntb <= right)   tmp[cnt++] = nums[cntb++];
        for(int i = left; i <= right; i++)  nums[i] = tmp[i-left];
    }

    void mergeSort(int[] nums, int left, int right) {
        if(left >= right)   return;
        int mid = (left + right)/2;
        mergeSort(nums, left, mid);
        mergeSort(nums, mid+1, right);
        merge(nums, left, right);
    }

5、堆排序

堆排序是利用最大堆的特性(根是所有对结点中键值最大的),首先构建堆(O(n)),每次将根与堆的最后一个元素进行交换,并且将堆的大小减少1(并没有把最后的堆元素删除),调整堆使其保证最大堆的特性,当堆的大小减为1时,再层次遍历堆,完成排序。
    //建堆
    void buildHeap(int[] nums, int size) {  
        for(int i = (size >>>1); i >= 1; i--) {
            heapify(nums, size, i);
        }
    }
    
    //调整堆
    void heapify(int[] nums, int size, int i) {
        int l = i << 1;
        int r = (i << 1) + 1;
        int largest  = i;
        if(l <= size && nums[l] > nums[i])
            largest = l;
        if(r <= size && nums[r] > nums[largest])
            largest = r;
        if(largest != i) {
            int tmp = nums[largest];
            nums[largest] = nums[i];
            nums[i] = tmp;
            heapify(nums, size, largest);
        }
    }
    
    //堆排序
    void heapSort(int[] nums, int size) {        //nums从1开始, size是数据的实际大小(不包括下标为0的元素)
        buildHeap(nums, size);
        for(int i = size; i >= 2; i--) {
            int tmp = nums[i];
            nums[i] = nums[1];
            nums[1] = tmp;
            heapify(nums, i-1, 1);
        }
    }

6、快速排序

和归并排序相同,快排采用分治的策略,在序列中选取一个pivot, 把一个序列分为两个子序列,使得一个序列的所有值都小于等于pivot, 另一个序列的所有值都大于等于pivot,之后递归处理两个子序列。快排是不稳定排序,如3,4,5,4,1,2,pivot为3。以下为Java实现。快排的平均复杂度为O(nlogn),最坏的复杂度为O(n^2)。
    void swap(int[] nums, int x, int y) {
        int temp = nums[x];
        nums[x] = nums[y];
        nums[y] = temp;
    }

    void quickSort(int[] nums, int start, int end) {
        if (start >= end)
            return;
        int pivot = nums[start];
        int left = start, right = end-1;
        swap(nums, start, end);
        while (left < right) {
            while (left < right && nums[left] < pivot)
                left++;
            while (left < right && nums[right] >= pivot)
                right--;
            if(left < right)
                swap(nums, left, right);
        }
        swap(nums, left, end);
        quickSort(nums, start, left - 1);
        quickSort(nums, left + 1, end);
    }

7、计数排序

计数排序直接使用一个数组来保存该元素出现的次数,之后对所有的计数进行累加,反向填充目标数组。一下为Java实现。
    int[] countingSort(int[] nums) {
       int[] ans = new int[nums.length];
       int min =  nums[0], max = nums[0];
       for(int i = 0; i < nums.length; i++) {
           min = Math.min(min, nums[i]);
           max = Math.max(max, nums[i]);
       }
       int[] c = new int[max-min+1];
       for(int i = 0; i < nums.length; i++) {
           c[nums[i] -  min]++;
       }
       for(int i = 1; i < c.length; i++)
           c[i] += c[i-1];

       for(int i = nums.length-1; i >= 0; i--)
           ans[--c[nums[i]-min]] = nums[i];
       return ans;
   }

8、基数排序

基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数进行排序。其复杂度为O(dn),d为整数位数。一下为Java代码实现。
    int maxbit(int nums[], int n) //辅助函数,求数据的最大位数
    {
        int maxnums = nums[0];		///< 最大数
        /// 先求出最大数,再求其位数,这样有原先依次每个数判断其位数,稍微优化点。
        for (int i = 1; i < n; ++i)
        {
            if (maxnums < nums[i])
                maxnums = nums[i];
        }
        int d = 1;
        int p = 10;
        while (maxnums >= p)
        {
            p *= 10;
            ++d;
        }
        return d;
    }

    void radixsort(int nums[], int n) //基数排序
    {
        int d = maxbit(nums, n);
        int[] tmp = new int[n];
        int[] count = new int[10]; //计数器
        int i, j, k;
        int radix = 1;
        for(i = 1; i <= d; i++) //进行d次排序
        {
            for(j = 0; j < 10; j++)
                count[j] = 0; //每次分配前清空计数器
            for(j = 0; j < n; j++)
            {
                k = (nums[j] / radix) % 10; //统计每个桶中的记录数
                count[k]++;
            }
            for(j = 1; j < 10; j++)
                count[j] = count[j - 1] + count[j]; //将tmp中的位置依次分配给每个桶
            for(j = n - 1; j >= 0; j--) //将所有桶中记录依次收集到tmp中
            {
                k = (nums[j] / radix) % 10;
                tmp[count[k] - 1] = nums[j];
                count[k]--;
            }
            for(j = 0; j < n; j++) //将临时数组的内容复制到nums中
                nums[j] = tmp[j];
            radix = radix * 10;
        }
    }







  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值