几种排序方法

一、基于比较的排序

假设一个有N个数的数组

1、冒泡排序

思想: 冒泡排序是从0位置开始每次把相邻的两个数进行比较,如果0位置的数比较大,则和后一个数继续进行调换,直到调换到末尾,则已经把最大的数放到了最后,则排好了第N-1个位置上的数;接着重复上述的过程,直到排好0位置上的数结束。
实现:

    public static void bubbleSort(int arr[])
    {
        if (arr == null || arr.length<2)
            return;
        for (int e = arr.length-1; e>0; e--)
        {
            for (int i=0; i<e; i++)
            {
                if (arr[i] > arr[i+1])
                    swap(arr, i, i+1);
            }
        }
    }
    public static void swap(int arr[], int a, int b)
    {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b]= temp;

        //利用异或^运算来交换两个数的位置,暂时还没弄懂
//        arr[a] = arr[a] ^ arr[b];
//        arr[b] = arr[a] ^ arr[b];
//        arr[a] = arr[a] ^ arr[b];

    }

算法复杂度: 冒泡排序的时间复杂度是O(N2)
冒泡排序第一次遍历N次,第二次遍历N-1次,一直到1,求和,就是O(N2)。

2、插入排序

思想: 插入排序就是先排好前n-1个数,然后把第n个数插入到前n-1个数中,就和整理扑克牌一样。
其实就是先排0 ~ 1个数,再排0~2,直到0 ~ n.
实现:

    public static void insertionSort(int arr[])
    {
        if (arr == null || arr.length< 2)   //如果数组为空或者只有一个数,则直接返回
            return;
        for (int i = 1; i<arr.length; i++)   //否则开始进行比较
        {
            for (int j = i-1; j>= 0 && arr[j]>arr[j+1]; j--)
                swap(arr, j, j+1);
        }
    }
    public static void swap(int arr[], int a, int b)
    {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

**算法复杂度:**插入排序的算法复杂度为O(N2)

3、选择排序

思想: 每次把最小的数放在第一个,遍历0~N-1,把最小的数放第一个;然后遍历1 ~N-1,把最小的数放在第一位;
实现:

    public static void selectionSort(int arr[])
    {
        if (arr == null || arr.length <2)
            return;
        for (int i = 0; i < arr.length; i++)
        {
            int minindex =i;
            for (int j= i+1; j<arr.length; j++)
            {
                minindex = arr[j] < arr[minindex] ? j : minindex;
            }
            swap(arr, i, minindex);
        }
    }
    public static void swap(int arr[], int a, int b)
    {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

算法复杂度: 选择排序的算法复杂度是O(N2),同样的是第一次遍历N,第二次遍历N-1,类推,所以最后复杂度为O(N2).

冒泡排序和选择排序的区别

冒泡排序和选择排序的区别:
1、冒泡排序,每次是相邻两个数进行比较,把较大的数放在后面;每一次比较如果位置不对都需要调换位置;这个排序是稳定的(不破坏数组本身的顺序);冒泡排序是数找位置(最大的数放最后)。
2、选择排序,每次遍历选择出整个数组最小的数,把最小的数放在最前面;每一轮比较后,最小数和最前面的数换一次位置;这个排序是不稳定的;选择排序是位置找数(最前面的位置放最小的数)。

4、归并排序

思想: 归并排序是先将数组分为左右两部分,然后分别对左边和右边进行排序,最后利用外排(借助一个数组来存储排序的结果)合并两边的排序。
实现:

    public static void mergeSort(int arr[])
    {
        if (arr == null || arr.length <2)
            return;
        mergeSort(arr, 0, arr.length-1);  //调用归并排序
    }
    public static void mergeSort(int arr[], int l, int r)
    {
        if (l == r)
            return;
        int mid = l + ((r -l)>>1);   //将数组划分
        mergeSort(arr, l, mid);   //对左边进行排序
        mergeSort(arr, mid+1, r);  //对右边进行排序
        merge(arr, l, mid, r);   //将两边合并起来
    }
    public static void merge(int arr[], int l, int mid, int r)
    {
        int help [] = new int[r-l+1];   //设置一个额外的数组来存储排好的顺序
        int p1 = l;   //左边的指针
        int p2 = mid+1;  //右边的指针
        int i = 0;
        while (p1 <= mid && p2 <= r)   //比较左右两边的数,哪一边小则哪一边放入到help数组中,同时指针加1,一直比较,直到一方指针到达末尾
        {
            help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
        }
        //以下两个while必会执行其中的一个
        while (p1 <= mid)   //右边的指针先到达末尾
            help[i++] = arr[p1++];
        while (p2 <= r)   //左边的指针先到达末尾
            help[i++] = arr[p2++];
        for ( i=0; i<help.length; i++)  //把排好序的数再放回到arr数组中
            arr[l+i]= help[i];
    }

算法复杂度: 归并排序的算法复杂度是O(N*logN)


这里利用master公式:
master公式:T(N) = aT(N/b) + O(Nd) (N/b的意思是把样本为N划分为了多大的小样本, a是同样的小样本执行的次数,d是其他常数级别的操作)
logba(log以b为底,a的对数)
1) logba > d, 复杂度O(N的logba 方) (很难打出来,所以用语言描述)
2) logba = d, 复杂度O(Nd*logN)
3) logba < d, 复杂度O(Nd)


在归并排序中,把数组划分为了左右两边,所以b是2,然后对左右两边分别进行了排序,所以a是2,剩下的merge操作是N的一次的操作,所以d是1, 所以归并排序的时间复杂度为O(N*logN)。
额外的空间复杂度为O(N)。(额外的空间复杂度是help数组)

5、随机快排(用得最多的排序方法)

思想:

  • 经典快排
    将数组的最后一个数作为x,把比x小的数放在数组的左边,把比x大的数放在数组的右边;然后再分别取左边和右边的数的最后一个数作为x,分别执行上述过程。
  • 改进快排
    将数组的最后一个数作为x,把比x小的数放在数组的左边,和x一样大的放在数组的中间,把比x大的数放在数组的右边;然后再分别取左边和右边的数的最后一个数作为x,分别执行上述过程。
  • 随机快排
    随机选定数组中的一个数,把它和最后一个数进行调换,然后重复改进快排的过程。
  • 三种快排的区别
    经典快排每次只能确定一个数的位置,改进快排每次可以确定所有和x相等值的数的位置;经典快排和改进快排每次都是取最后一个数作为x,这样的作法的时间复杂度和数组本身的数据情况好坏有极大的关系,最坏情况下是O(N2),最好情况是O(N* logN)( 这 里 我 还 没 弄 懂 , 做 个 标 记 \color{red}{这里我还没弄懂,做个标记} );随机快排随机选取一个数作为x,这样时间复杂度就是一个长期的期望值,最终就是O(N*logN)。

实现:
随机快排

   public static void quickSort(int [] arr)
    {
        if (arr == null || arr.length < 2)   //数组为空或者数组只有一个数,直接返回,不需要排序
            return;
        quickSort(arr, 0, arr.length-1);   //反之进行随机快排
    }
    public static void quickSort(int [] arr, int l, int r)
    {
        if (l < r)
        {
            swap(arr, l+(int)(Math.random()*(r-l+1)), r);   //随机选中一个数和数组最后一个数进行交换
            int p[] = partition(arr, l, r);   //对数组进行划分,比x小的放左边,相等的放中间,大的放右边
            quickSort(arr, l, p[0]-1);   //对左边进行排序
            quickSort(arr, p[1]+1, r);   //对右边进行排序
        }
    }
    public static int[] partition(int [] arr, int l, int r)
    {
        int less = l-1;   //小于x的指针的位置,每有一个小于x的数则,less+1位置上的数和小于x的数交换,less+1,l加1
        int more = r;    //大于x的指针的位置,首先指向最后一个数,每有一个大于x的数,则和more指针的前一个数交换,more+1
        while (l < more)  //l和more相遇则所有的数都比较完毕
        {
            if (arr[l] < arr[r])
                swap(arr, l++, ++less);
            else if (arr[l] > arr[r])
                swap(arr, --more, l);
            else
                l++;
        }
        swap(arr, r, more);    //最后把最后一个数即x和more指向的数进行交换,则more指针后面的数都是比x大的
        return new int[]{less + 1, more};   //返回等于x的数组的区间
    }
    public static void swap(int [] arr, int a, int b)
    {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

算法复杂度:
随机快排的算法复杂度是O(N*logN)。

6、堆排序

首先了解的概念,堆,实际上就是一个完全二叉树(完全二叉树包括满二叉树和非满二叉树,但是从左向右是依次补齐的)。
在这里插入图片描述再了解两个概念:大根堆和小根堆
1)大根堆:在完全二叉树中,任何一棵子树的最大值都是这颗子树的头部,所形成的结构叫大根堆。
2)小根堆:在完全二叉树中,任何一棵子树的最小值都是这颗子树的头部。
![大根堆](https://img-blog.csdnimg.cn/20200731144039133.png在这里插入图片描述
节点i:
1、左孩子的下标:2i +1;
2、右孩子的下标:2
i +2;
3、父节点的下标:(i-1)/2。
思想:
堆排序思想:
1)首先建立一个大根堆;
2)堆顶的数和最后一个数交换,heapsize-1;(heapsize是堆的大小)
3)进行heapfiy操作;(heapfiy是将当前堆调整为大根堆)
4)重复操作2)和3)。
在这里插入图片描述实现:
建立一个大根堆heapInsert(int[] arr, int index)
进来的数和根节点比,然后更新index
将堆调整为大根堆heapfiy(int [] arr, int index, int size):
比较index的两个左右子节点,较大的为largest,然后比较largest和index值的大小,更新largest,如果largest和index一样大,说明不需要调整了,break;否则交换largest和index的值,更新index。

    public static void heapSort(int [] arr)
    {
        if (arr == null || arr.length < 2)
            return;
        for (int i = 0; i < arr.length; i++)
        {
            heapInsert(arr, i);      //建立大根堆
        }
        int size = arr.length;
        swap(arr, 0, --size);   //交换根节点(即最大的值)和最后一个值,堆的大小减1
        while (size > 0)     //只要堆的大小还大于0,就一直进行上述的操作
        {
            heapfiy(arr, 0, size);  //先把之前的堆再次调整为大根堆
            swap(arr, 0, --size);  //重复交换根节点和最后一个值,堆的大小减1
        }
    }
    public static void heapInsert(int [] arr, int index)   //建立大根堆
    {
        while (arr[index] > arr[(index-1)/2])    //比较插入的点和其根节点哪个大,把较大的点放到根节点,更新index
        {
            swap(arr, index, (index-1)/2);
            index = (index - 1) / 2;
        }
    }
    public static void heapfiy(int [] arr, int index, int size)   //把改变后的堆调整为大根堆
    {
        int left = index * 2 + 1;      //index的左孩子
        while (left < size)    //左孩子小于size,说明还有右孩子
        {
            int largest = left + 1 < size && arr[left+1] > arr[left] ? left+1 : left;  //比较左右两个孩子的大小
            largest = arr[largest] > arr[index] ? largest : index;   //比较index和左右孩子中较大的孩子哪个大,更新largest
            if (largest == index)   //比较largest和index,如果相等,则说明不需要调整堆,
                break;
            swap(arr, largest, index);  //否则交换index和largest
            index = largest;
            left = index * 2 + 1;
        }
    }
    public static void swap(int [] arr, int a, int b)
    {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

算法复杂度:
堆排序的算法复杂度是O(NlogN),( 不 太 会 分 析 这 里 的 复 杂 度 \color{red}{不太会分析这里的复杂度} )。

二、非基于比较的排序

1、桶排序

思想:
桶排序就是找出数组中最大的值,然后建立相应的桶,把对应的数字放到对应的桶中,最后再从桶中把数倒出来。
实现:

   public static void bucketSort(int [] arr)
    {
        if (arr == null || arr.length < 2)
            return;
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < arr.length; i++)
            max = Math.max(max, arr[i]);
        int[] bucket = new int[max+1]; //max加1是因为还要准备0的桶
        for (int i = 0; i < arr.length; i++)   //把数组中的数依次放入对应的桶中
        {
            bucket[arr[i]]++;
        }
        int i = 0;
        for (int j = 0; j < bucket.length; j++)  //取出对应桶中的数
        {
            while(bucket[j]-- > 0)
            {
                arr[i++] = j;
            }
        }
    }

时间复杂度:

2、计数排序

3、基数排序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值