排序

本文详细介绍了排序算法,包括稳定性、插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序和归并排序,强调了各种排序算法的时间复杂度和稳定性特点。对于快速排序,提到了优化手段,而归并排序因其稳定性与较高的时间复杂度被提及。
摘要由CSDN通过智能技术生成
  1. 概念
    1.1 排序
    排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。平时的上下文中,如果提到排序,通常指的是排升序
    1.2 稳定性
    两个相等的数据,如果经过排序后,排序算法·能保证其相对位置不发生变化,则我们称该算法是具备稳定性的排序算法。
  2. 七大基于比较的排序
    常见排序算法:
    插入排序:直接插入排序、希尔排序
    选择排序:选择排序、堆排序
    交换排序:冒泡排序、快速排序
    归并排序:归并排序
  3. 插入排序
    3.1 原理
    整个区间被分为有序区间和无序区间,每次选择无序区间的第一个元素,在有序区间内选择合适的位置插入
//插入排序
    public static void insertSort(int[] arr){
   // 这个循环就是在控制代码进行 N 次插入过程
        for(int bound = 1;bound < arr.length;bound++) {
            //此处的循环是用来表示未排序区间
            //已排序区间[0,bound)
            //未排序区间[bound,length)
            //此处的v就是待排序区间的第一个元素,也就是要插入的元素
            int v = arr[bound];
            int cur = bound - 1;
            for (; cur >= 0; cur--) {
                if (arr[cur] > v) {
                //如果当前比较的元素比插入元素大,就把当前元素往后移
                    arr[cur + 1] = arr[cur];
                } else {
                    //此时就说明找到了v要插入的位置
                    break;
                }
            }
            arr[ cur + 1] = v;
        }
    }

    public static void main(String[] args) {
        int[] arr = {6,4,9,2,0,1,8,5};
        insertSort(arr);
        System.out.println(Arrays.toString(arr));
    }

3.2 重要性质:

  1. 如果当前数组比较小,这个时候速度就很快
    2)如果当前数组相对有序,这个时候的速度也很快

3.3
时间复杂度:O(N^2)
空间复杂度:O(1)
稳定性:稳定排序

4.希尔排序
gap分组系数
gap的值表示分的组数,同时也表示每组的相邻元素的下标差值。
步骤:
1)针对每个分组,分别进行插入排序
2)调整分组系数,再次进行插入排序,直到gap的值为1,这是就与插入排序相同

希尔序列:
gap= arr.length/2, arr.length/4, arr.length/8, …1,这样来取的话,虽然时间复杂度也是O(N^2),但要比单纯的插入排序要快一些。

//希尔排序
        public static void shellSort(int[] arr) {
        //指定gap序列
            int gap = arr.length / 2;
            while(gap >= 1) {
                _shellSort(arr,gap);
                gap = gap / 2;
            }
        }
        public static void _shellSort(int[] arr,int gap) {
            int bound = gap;
            for (; bound < arr.length; bound++) {
                //待插入元素
                int v = arr[bound];
                //当前将要比较元素的下标
                int cur = bound - gap;
                for (; cur >= 0; cur -= gap) {
                    if (arr[cur] > v) {
                        arr[cur + gap] = arr[cur];
                    } else {
                        break;
                    }
                }
                arr[cur + gap] = v;
            }
        }
    public static void main(String[] args) {
        int[] arr = {6,4,9,2,0,1,8,5};
        shellSort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

最优时间复杂度:O(N^1.3)
空间复杂度:O(1)
稳定性:不稳定排序

5.选择排序
核心思路:基于打擂台的方式,每次从待排序区间中找出最小值,放到擂台上,(擂台就是待排序区间的最开始的位置)

//选择排序

    public static void selectSort(int[] arr){
        // 设置一个bound值,表示已排序区间与未排序区间的边界值
        //[0,bound) 已排序区间
        //[bound,length) 未排序区间
        int bound = 0;
        for(;bound < arr.length; bound++) {
            //里层循环表示要进行打擂台的过程
            int cur = bound + 1;
            for (; cur < arr.length; cur++) {
                if (arr[bound] > arr[cur]) {
                    swap(arr, bound, cur);
                }
            }
        }
    }
    public static void swap(int[] arr,int x,int y){
        int tmp = arr[x];
        arr[x] = arr[y];
        arr[y] = tmp;
    }

    public static void main(String[] args) {
        int[] arr = {6,4,9,2,0,1,8,5};
        selectSort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

时间复杂度:O(N^2)
空间复杂度:O(1)
稳定性:不稳定排序

6.堆排序

可以理解成是对选择排序的一种优化
如果是升序排序
1)直观上,建立一个小堆,每次取堆顶元素,循环取N次,就得到了一个有序序列
2)建立一个大堆,交换堆顶元素与数组的最后一个元素(这时最大值就来到了数组末尾),前面的部分就从0开始进行向下调整

向下调整的前提:
当前节点的左右子树,都是符合堆的要求,只有根节点不符合,这个时候就可以针对根节点进行向下调整了。

//堆排序

public static void heapSort(int[] arr) {
    //1.创建堆
    createHeap(arr);
    //2.循环进行堆顶元素与数组最后一个元素进行交换,并删除最后一个元素
    int heapSize = arr.length;//数组的大小
    for (int i = 0; i < arr.length; i++) {
        swap(arr, 0, heapSize - 1);
        //删除最后一个元素
        heapSize--;
        shiftDown(arr, heapSize, 0);
    }
}

public static void shiftDown(int[] arr, int size, int index) {
    int parent = index;
    int child = 2 * parent + 1;
    while (child < size) {
        if (child + 1 < size && arr[child + 1] > arr[child]) {
            child = child + 1;
        }
        if (arr[parent] < arr[child]) {
            swap(arr, parent, child);
        } else {
            break;
        }
      //重新调整parent与child的值
        parent = child;
        child = 2 * parent + 1;
    }
}


public static void createHeap(int[] arr) {
    //从后往前遍历,i=arr.length-1-1,表示数组的最后一个元素(左子树)
    for (int i = (arr.length - 1 - 1) / 2; i >= 0; i--) {
        shiftDown(arr, arr.length, i);
    }
}
public static void swap(int[] arr,int x,int y){
    int tmp = arr[x];
    arr[x] = arr[y];
    arr[y] = tmp;
}
public static void main(String[] args) {
    int[] arr = {6,4,9,2,0,1,8,5};
    heapSort(arr);
    System.out.println(Arrays.toString(arr));
   }
}

时间复杂度:O(NlogN)
空间复杂度:O(1)
稳定性:不稳定排序

  1. 冒泡排序
    基本思路:
    比较两个相邻的元素,看是否符合升序要求,如果不符合就交换。
    一趟遍历下去:
    就能找到最大值,并且把最大值放在最后(从前往后遍历)
    就能找到最小值,并且把最小值放在最前(从后往前遍历)
//冒泡排序
    public static void bubbleSort(int[] arr){
        //从后往前遍历
        int bound = 0;
        for(;bound < arr.length; bound++){
            int cur = arr.length - 1;
            for(;cur > bound; cur--){
                if(arr[cur - 1] > arr[cur]) {
                    swap(arr,cur - 1,cur);
                }
            }
        }
    }
    public static void swap(int[] arr,int x,int y){
        int tmp = arr[x];
        arr[x] = arr[y];
        arr[y] = tmp;
    }

    public static void main(String[] args) {
        int[] arr = {6,4,9,2,0,1,8,5};
       // insertSort(arr);
        //shellSort(arr);
       // selectSort(arr);
        //heapSort(arr);
        bubbleSort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

时间复杂度:O(N^2)
空间复杂度:O(1)
稳定性:稳定排序

8.快速排序
选定基准值
若选数组中最后一个元素为基准值,让left指向第一个元素,right只想最后一个元素,left向右走,找到比基准值大的元素,right向左走,找到比基准值小的元素,这时,交换两元素的位置,继续left++,right–,当left与right重合时,将当前值与基准值位置交换。这时基准值的左边都小于基准值,右边都大于基准值。

快速排序的优化手段:
1)基准值可以采用三数取中的方式
2)如果数组很大,就可能导致递归深度很深,但当前待处理的区间已经很小了,这是可以不继续使用递归,而是直接插入排序
3)如果递归深度已经达到一定的深度,并且当前这个区间的元素数目仍很多,这时可以针对这个区间使用堆排序

//快速排序(递归)

public static void quickSort(int[] arr){
    //使用一个辅助方法进行递归
    //这时辅助方法里多了两个参数,用来表示对数组的哪个区间进行整理
    _quickSort(arr,0,arr.length - 1);
}
public static void _quickSort(int[] arr,int left,int right) {
    if (left >= right) {
        //区间里没有或只有一个元素,不必排序
        return;
    }
    //index表示left和right的重合位置
    int index = partition(arr, left, right);
    //递归处理左半区间
    _quickSort(arr, left, index - 1);
    //递归处理右半区间
    _quickSort(arr, index + 1, right);

}
public static int partition(int[] arr,int left,int right){
    //选取基准值
    int v = arr[right];
    int i = left;
    int j = right;
    while(i < j) {
        //先从左往右找一个比基准值大的元素
        while (i < j && arr[i] <= v) {
            i++;
        }
        //从右往左找出一个比基准值小的元素
        while (i < j && arr[j] >= v) {
            j--;
        }
        swap(arr, i, j);
    }
    //当i j 重合的时候,就将当前元素与基准值位置交换
    swap(arr,i,right);
    return i;

}
public static void swap(int[] arr,int x,int y){
    int tmp = arr[x];
    arr[x] = arr[y];
    arr[y] = tmp;
}

public static void main(String[] args) {
    int[] arr = {6,4,9,2,0,1,8,5};
    quickSort(arr);
    System.out.println(Arrays.toString(arr));
       }
}

//快速排序(非递归)

public static void quickSortByLoop(int[] arr) {
    //建立一个栈,栈里面存放要去处理的空间
    Stack<Integer> stack = new Stack<>();
    //把第一组要去处理的区间入栈
    stack.push(0);
    stack.push(arr.length - 1);
    //循环取栈顶元素的区间,进行partition操作
    while (!stack.isEmpty()) {
        int end = stack.pop();
        int beg = stack.pop();
        if (beg >= end) {
            //区间只有一个或没有元素,不需排序
            return;
        }
        //调用partition方法
        int index = partition(arr, beg, end);
        //把得到的子区间再入栈

        stack.push(index + 1);
        stack.push(end);

        stack.push(beg);
        stack.push(index - 1);
        
    }
}

public static int partition(int[] arr, int left, int right) {
    //选取基准值
    int v = arr[right];
    int i = left;
    int j = right;
    while (i < j) {
        //先从左往右找一个比基准值大的元素
        while (i < j && arr[i] <= v) {
            i++;
        }
        //从右往左找出一个比基准值小的元素
        while (i < j && arr[j] >= v) {
            j--;
        }
        swap(arr, i, j);
    }
    //当i j 重合的时候,就将当前元素与基准值位置交换
    swap(arr, i, right);
    return i;

}



public static void main(String[] args) {
    int[] arr = {6,4,9,2,0,1,8,5};
  
    quickSortByLoop(arr);
    System.out.println(Arrays.toString(arr));
      }
}

时间复杂度:
平均:O(NlogN)
最坏:O(N^2)
空间复杂度:
平均:O(logN)
最坏:O(N)
稳定性:不稳定排序

9.归并排序

核心思路:就是将两个有序数组合并成一个有序数组

//递归

 //归并排序
    public static void mergeSort(int[] arr){
        //使用一个方法来辅助递归,多出的两个参数表示是从哪个区间进行排序的
        //区间为前闭后开区间
        _mergrSort(arr,0,arr.length);
    }

    public static void _mergrSort(int[] arr,int left,int right){
        if(right - left <= 1) {
            //表示这个区间的元素为1个或者没有元素,直接返回,无需排序
            return;
        }
        //把区间一分为二
        //[left,mid)和[mid,left)两个区间
        int mid = (left + right) / 2;
        //当左半边区间通过_mergeSort递归完成,表示该区间已经是一个有序区间了
        _mergrSort(arr,left,mid);
        //当左半边区间通过_mergeSort递归完成,表示该区间已经是一个有序区间了
        _mergrSort(arr,mid,right);
        //接下来就将两个有序数组进行合并
        merge(arr,left,mid,right);
    }

    public static void merge(int[] arr,int left,int mid,int right) {
        if (left >= right) {
            return;
        }
        //创建一个临时数组,用来存放新合成的数组
        //我们需要在临时数组中存放right-left个元素
        int[] tmp = new int[right - left];
        //要把新的元素放入临时数组的下标中
        int tmpsize = 0;
        //l和r分别表示两个数组最开始的位置
        int l = left;
        int r = mid;
        while (l < mid && r < right) {
            //归并排序是一个稳定排序,所以条件不能是arr[l]<arr[r]
            if (arr[l] <= arr[r]) {
                tmp[tmpsize] = arr[l];
                tmpsize++;
                l++;
            } else {
                tmp[tmpsize] = arr[r];
                tmpsize++;
                r++;
            }
        }
        //其中一个数组已经遍历完成,这时就把另一个数组依次插入临时数组中
        while (l < mid) {
            tmp[tmpsize] = arr[l];
            tmpsize++;
            l++;
        }
        while (r < right) {
            tmp[tmpsize] = arr[r];
            tmpsize++;
            r++;
        }
        //这时数组全部放入临时数组中,需要把临时数组中的元素全部拷贝回原数组arr[]
        //+left的原因是[left,right)这个区间不一定是从0开始的
        for(int i = 0; i < tmp.length; i++){
                arr[left + i] = tmp[i];
            }
    }

    public static void main(String[] args) {
        int[] arr = {6,4,9,2,0,1,8,5};
        mergeSort(arr);
        System.out.println(Arrays.toString(arr));
       }
    }
 //归并排序  非递归
    public static void mergeSortByLoop(int[] arr){
        //gap为当前待合并的有序数组的长度
        for(int gap = 1; gap < arr.length; gap *= 2) {
            //外层循环
            //第一次是把数组长度为1的两个有序数组两两合并
            //第二次是把数组长度为2的两个有序数组两两合并
            //第三次是把数组长度为4的两个有序数组两两合并
            for (int i = 0; i < arr.length; i += 2 * gap) {
                int left = i;
                int mid = i + gap;
                if (mid > arr.length) {
                    mid = arr.length;
                }
                int right = i + 2 * gap;
                if (right > arr.length) {
                    right = arr.length;
                }
                merge(arr, left, mid, right);
            }
        }
    }

public static void merge(int[] arr,int left,int mid,int right) {
        if (left >= right) {
            return;
        }
        //创建一个临时数组,用来存放新合成的数组
        //我们需要在临时数组中存放right-left个元素
        int[] tmp = new int[right - left];
        //要把新的元素放入临时数组的下标中
        int tmpsize = 0;
        //l和r分别表示两个数组最开始的位置
        int l = left;
        int r = mid;
        while (l < mid && r < right) {
            //归并排序是一个稳定排序,所以条件不能是arr[l]<arr[r]
            if (arr[l] <= arr[r]) {
                tmp[tmpsize] = arr[l];
                tmpsize++;
                l++;
            } else {
                tmp[tmpsize] = arr[r];
                tmpsize++;
                r++;
            }
        }
        //其中一个数组已经遍历完成,这时就把另一个数组依次插入临时数组中
        while (l < mid) {
            tmp[tmpsize] = arr[l];
            tmpsize++;
            l++;
        }
        while (r < right) {
            tmp[tmpsize] = arr[r];
            tmpsize++;
            r++;
        }
        //这时数组全部放入临时数组中,需要把临时数组中的元素全部拷贝回原数组arr[]
        //+left的原因是[left,right)这个区间不一定是从0开始的
        for(int i = 0; i < tmp.length; i++){
                arr[left + i] = tmp[i];
            }
    }

   

    public static void main(String[] args) {
        int[] arr = {6,4,9,2,0,1,8,5};
        mergeSortByLoop(arr);
        System.out.println(Arrays.toString(arr));
    }
}

时间复杂度:O(NlogN)
空间复杂度:O(N)
稳定性:稳定排序

排序总结·:

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值