排序算法归纳总结
一、排序算法分类
(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220727163456106.png)]
二、排序原理及代码实现
1、冒泡排序
1.1 原理
冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
优化:
因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。(这里说的优化,可以在冒泡排序写好后,在进行)
1.2 代码实现
//冒泡排序,从小到大
public void bubbleSort(int[] arr) {
//优化:标记第几轮有没有进行交换,没有交换则说明已经有序,提前结束
boolean flag = false;
for (int i=0;i<arr.length-1;i++){
//第i+1轮排序后得到第i大的值,下一轮比较下标为0-arr.length-1-i的数,后面的已经有序。
flag = false;
for(int j=0;j<arr.length-1-i;j++){
if (arr[j] > arr[j+1]){//前面的元素比后面元素大,交换
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
flag = true;
}
}
if(!flag){//没有交换,提前结束
break;
}
}
}
2、选择排序
2.1 原理
2.2 代码实现
//选择排序,从小到大
//法一:
public void SelectSort(int[] arr) {
for (int i=0;i<arr.length;i++){
//确定arr[i]的值为第i+1小的
for (int j=arr.length-1;j>i;j--){
if (arr[j]<arr[i]){//如果i+1 - arr.length-1中的值比当前arr[i]小,交换
int temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
}
}
}
//法二:
public void SelectSort(int[] arr) {
for (int i=0;i<arr.length;i++){
//确定arr[i]的值为第i+1小的
int min = arr[i];
int minIndex = i;
for (int j=arr.length-1;j>i;j--){
if (arr[j] < min){//如果i+1 - arr.length-1中的值比当前arr[i]小,交换
min = arr[j];
minIndex = j;
}
}
arr[minIndex] = arr[i];
arr[i] = min;
}
}
3、插入排序
3.1 原理
3.2 代码实现
//插入排序,从小到大
//法一:
public void InsertSort(int[] arr) {
//控制拿去每一个元素
//注意这里从1开始
for(int i=1;i<arr.length;i++){
//比较次数
for (int j=i;j>0;j--){
//是否小于前面的元素,和前面大于当前元素的交换,直到前一个元素小于当前元素
if (arr[j] < arr[j-1]){
int temp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = temp;
}else {
break;
}
}
}
}
//法二:插入时,可以二分查找插入,提高效率。
public void InsertSort(int[] arr) {
//控制拿去每一个元素
//注意这里从1开始
for(int i=1;i<arr.length;i++){
//比较次数
for (int j=i;j>0;j--){
//是否小于前面的元素,和前面大于当前元素的交换,直到前一个元素小于当前元素
if (arr[j] < arr[j-1]){
int temp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = temp;
}else {
break;
}
}
}
}
4、快速排序
4.1 原理
原理:分治法。每次选出一个基准数据(一般认为是第一个数据)。将基准插入到合适位置(这个位置左边的数都小于基准,右边的数都大于基准)。递归实现。
4.2 代码实现
class Solution {
Random random = new Random();
public int[] sortArray(int[] nums) {
quickSort(nums,0,nums.length-1);
return nums;
}
public void quickSort(int[] nums,int start,int end){
if(start < end){
int center = partition(nums,start,end);
quickSort(nums,start,center-1);
quickSort(nums,center+1,end);
}
}
public int partition(int[] nums, int l, int r) {
int ran = random.nextInt(r - l + 1)+l;
swap(nums,r,ran);
int pivot = nums[r];
int i = l - 1;
for (int j = l; j <= r - 1; ++j) {
if (nums[j] <= pivot) {
i = i + 1;
swap(nums, i, j);
}
}
swap(nums, i + 1, r);
return i + 1;
}
public void swap(int[] nums,int i,int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
//单链表快排实现
public class Solution {
/**
*
* @param head ListNode类 the head node
* @return ListNode类
*/
public ListNode sortInList (ListNode head) {
// write code here
if(head == null || head.next == null){
return head;
}
return quickSort(head);
}
public ListNode quickSort(ListNode head){
if(head == null || head.next == null){
return head;
}
ListNode lhead = new ListNode(-1);
ListNode mhead = new ListNode(-1);
ListNode rhead = new ListNode(-1);
ListNode p = head;
//将链表分成三段,第一段都比枢轴小,第二段等于枢轴,第三段都大于枢轴
ListNode left = lhead;
ListNode mid = mhead;
ListNode right = rhead;
int pivot = head.val;
while(p != null){
if(p.val < pivot){
left.next = p;
left = left.next;
}else if(p.val == pivot){
mid.next = p;
mid = mid.next;
}else{
right.next = p;
right = right.next;
}
p = p.next;
}
left.next = null;
right.next = null;
mid.next = null;
//递归对第一段和第三段进行排序;因为第二段各元素相等,不用再排序
lhead.next = quickSort(lhead.next);
rhead.next = quickSort(rhead.next);
left = getTail(lhead);//获取链表最后一个元素
if(left != null){
left.next = mhead.next;
}
mid = getTail(mhead);//获取链表最后一个元素
if(mid != null){
mid.next = rhead.next;
}
return lhead.next;
}
public ListNode getTail(ListNode head){
if(head == null){
return null;
}
ListNode cur = head;
while(cur.next != null){
cur = cur.next;
}
return cur;
}
}
5、堆排序
5.1 原理
原理:生成一个大顶堆(或小顶堆),每次将堆顶与最后一个数据交换位置,取出堆顶,剩余的数据重新调整堆结构。这里调整堆结构不太好理解。
5.2 代码实现
//堆排序,实现的是小顶堆,即从小到大排序
public class HeapSort {
public static int[] sort(int[] arr){ //用数组来表示二叉树树结构
int[] arr2 = new int[arr.length+1];
//向后移动一位,形成二叉树的位置关系,此时,开始的root=1;left = 2*root;right=2*root+1;
for (int i = 1; i < arr.length+1; i++) {
arr2[i] = arr[i-1];
}
int len = arr2.length;
while(len != 1){
arr2 = shiftHeapByInsert(arr2,len); //对数组[1,len]的位置形成大顶堆
swap(arr2,1,len-1); //将大顶堆的堆顶放入尾部(并非最后)
len--;
}
for (int i = 0; i < arr2.length-1; i++) {
arr[i] = arr2[i+1];
}
return arr;
}
public static int[] shiftHeapByInsert(int[] arr,int len){
int[] arr2 = arr;
//注意这里从下标2开始,下标为1的是树的根,即堆顶
for (int i = 2; i < len; i++) {
int k = i; //代表插入arr中的位置
arr2[k] = arr[i];
//每个元素通过与父节点对比,上浮找到自己的位置
while(k != 1){
int j = k/2; //arr[k] 的父节点为arr[k/2],即arr[j]
if(arr2[k] > arr2[j]){//当父节点的值小于子节点时,交换,直到去到对应的层数
swap(arr2,k,j);
k = j;
}else{
break;
}
}
}
return arr2;
}
}
6、希尔排序
6.1 原理
原理:将小于n的整数d1作为第一个距离增量,第一次让所有距离为d1的数据排序。第二次让所有为d2的数据排序(d2<d1),,,直到最后一次让所有距离为1的数据排序,也就是所有数据排序。这里的距离增量可以人为设置。其中每次让所有距离为d的数据排序的算法为直接插入排序。
6.2 代码实现
//希尔排序
public class ShellSort {
public static int[] sort(int[] arr){
int h=1; //增量
int len = arr.length;
while (h < len/3)
h = 3*h + 1; //可以调整,比如每次是两倍
while(h >= 1){
for (int i = h; i <len ; i++) {
for(int j = i; j >= h && arr[j]<arr[j-h] ; j -= h )
swap(arr, j, j-h);
}
h = h/3;
}
return arr;
}
}
//法二:
public void shellSort(int[] arr) {
int len = arr.length;
for(int gap=len/2;gap>=1;gap = gap/2){
for(int i=gap;i<len;i++){
for(int j=i;j>=gap && arr[j]<arr[j-gap];j -= gap){
swap(arr,j,j-gap);
}
}
}
}
7、归并排序
7.1 原理
原理:分治法。相当于将数组分成两份,将左右两边分别排好序之后再归并起来。用递归的方法实现。
7.2 代码实现
//归并排序,递归,分治法
//从小到大排序
public class MergeSort {
public static void sort(int[] arr){
mergeSort(arr,0,arr.length-1);
}
public static void mergeSort(int[] arr,int start,int end){
if(end - start == 1){
if(arr[start] > arr[end]){
swap(arr,start,end);
}
return;
}
if(end == start) return;
int center = (start+end)/2;
mergeSort(arr,start,center);
mergeSort(arr,center+1,end);
merge(arr,start,center,end);
}
//归并,相当于归并两个数组
public static void merge(int[] arr,int start,int center,int end){
int[] newArr = new int[end-start+1];
int i = start;
int j = center+1;
int k = 0;
while(i<=center && j<=end){
if(arr[i] < arr[j]){
newArr[k++] = arr[i++];
}else{
newArr[k++] = arr[j++];
}
}
if(i<=center){
for(;i<=center;i++)
newArr[k++] = arr[i];
}
if(j<=end){
for(;j<=end;j++)
newArr[k++] = arr[j];
}
k = 0;
for (int l = start; l <= end; l++) {
arr[l] = newArr[k++];
}
}
}
8、计数排序
8.1 原理
缺点/局限性:
- 当数据最大值和最小值差距过大时,并不适用于计数排序
- 当数列元素不是整数时,并不适用于计数排序。
8.2 代码实现
/**
* @author Ling
* @date 2022/7/28 11:54
*/
public class CountSort {
public static void main(String[] args) {
int[] arr = new int[]{3,8,-2,5,-7,21,6,3};
System.out.println("计数排序前:");
System.out.println(Arrays.toString(arr));
countSort(arr);
System.out.println("计数排序后:");
System.out.println(Arrays.toString(arr));
}
private static void countSort(int[] arr) {
// 找到数组中的最大最小值---> max:8
int min = arr[0];
int max = arr[0];
for(int i=1;i<arr.length;i++){
max = Math.max(max,arr[i]);
min = Math.min(min,arr[i]);
}
//计数
int[] count = new int[max - min + 1];
for(int i=0;i<arr.length;i++){
count[arr[i]-min]++;
}
for(int i=1;i<count.length;i++){
count[i] += count[i-1];
}
//通过计数确定当前数的位置,得到排序结果
int[] newArr = new int[arr.length];
for(int i=0;i<arr.length;i++){
newArr[count[arr[i]-min]-1] = arr[i];
count[arr[i]-min]--;
}
for(int i=0;i<arr.length;i++){
arr[i] = newArr[i];
}
}
}
9、桶排序
9.1 原理
桶排序可以看成是计数排序的升级版,它将要排的数据分到多个有序的桶里,每个桶里的数据再单独排序,再把每个桶的数据依次取出,即可完成排序。
桶排序:将值为i的元素放入i号桶,最后依次把桶里的元素倒出来。
9.2 代码实现
public class BucketSort{
public static void sort(int[] arr){
//最大最小值
int max = arr[0];
int min = arr[0];
int length = arr.length;
//得到最大最小值
for(int i=1; i<length; i++) {
if(arr[i] > max) {
max = arr[i];
} else if(arr[i] < min) {
min = arr[i];
}
}
//最大值和最小值的差
int diff = max - min;
//桶列表
ArrayList<ArrayList<Integer>> bucketList = new ArrayList<>();
for(int i = 0; i < length; i++){
bucketList.add(new ArrayList<>());
}
//每个桶的存数区间
float section = (float) diff / (float) (length - 1);
//数据入桶
for(int i = 0; i < length; i++){
//当前数除以区间得出存放桶的位置 减1后得出桶的下标
int num = (int) (arr[i] / section) - 1;
if(num < 0){
num = 0;
}
bucketList.get(num).add(arr[i]);
}
//桶内排序
for(int i = 0; i < bucketList.size(); i++){
//jdk的排序速度当然信得过
Collections.sort(bucketList.get(i));
}
//写入原数组
int index = 0;
for(ArrayList<Integer> arrayList : bucketList){
for(int value : arrayList){
arr[index] = value;
index++;
}
}
}
}
10、基数排序
10.1 原理
我们假设有一个待排序数组[53,3,542,748,14,214],那么如何使用基数排序对其进行排序呢?
首先我们有这样的十个一维数组,在基数排序中也叫桶。用桶排序实现。
一轮,以元素的个位数进行区分:[542,53,3,14,214,748]
第二轮,以元素的十位数进行区分:[3,14,214,542,748,53]
第三轮,以元素的百位数进行区分:[3,14,53,214,542,748]
10.2 代码实现
//法一:只能实现正数,负数会溢出
public class RaixSort{
public static void main(String[] args) {
int[] arr = { 53, 3, 542, 748, 14, 214 };
// 得到数组中最大的数
int max = arr[0];// 假设第一个数就是数组中的最大数
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
// 得到最大数是几位数
// 通过拼接一个空串将其变为字符串进而求得字符串的长度,即为位数
int maxLength = (max + "").length();
// 定义一个二维数组,模拟桶,每个桶就是一个一维数组
// 为了防止放入数据的时候桶溢出,我们应该尽量将桶的容量设置得大一些
int[][] bucket = new int[10][arr.length];
// 记录每个桶中实际存放的元素个数
// 定义一个一维数组来记录每个桶中每次放入的元素个数
int[] bucketElementCounts = new int[10];
// 通过变量n帮助取出元素位数上的数
for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
for (int j = 0; j < arr.length; j++) {
// 针对每个元素的位数进行处理
int digitOfElement = arr[j] / n % 10;
// 将元素放入对应的桶中
// bucketElementCounts[digitOfElement]就是桶中的元素个数,初始为0,放在第一位
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
// 将桶中的元素个数++
// 这样接下来的元素就可以排在前面的元素后面
bucketElementCounts[digitOfElement]++;
}
// 按照桶的顺序取出数据并放回原数组
int index = 0;
for (int k = 0; k < bucket.length; k++) {
// 如果桶中有数据,才取出放回原数组
if (bucketElementCounts[k] != 0) {
// 说明桶中有数据,对该桶进行遍历
for (int l = 0; l < bucketElementCounts[k]; l++) {
// 取出元素放回原数组
arr[index++] = bucket[k][l];
}
}
// 每轮处理后,需要将每个bucketElementCounts[k]置0
bucketElementCounts[k] = 0;
}
}
System.out.println(Arrays.toString(arr));//[3, 14, 53, 214, 542, 748]
}
}
//法二:
public void bucketSort(int[] arr){//重写bucketSort方法
int i = 0;
int j = arr.length-1;
//将负数都移到数组前面
while(i<j){
while(i<j && arr[i] <0){
i++;
}
while(i<j && arr[j] >= 0){
j--;
}
if (i < j){
swap(arr,i,j);
}
}
i--;
j = arr.length-1;
//上面的代码用于将arr分成两部分,由于负数总是比非负数小,从小到大排序也就是负数在前面
int index = 0;
for (int k = 0;k<=i;k++){
if (arr[k] == Integer.MIN_VALUE){
swap(arr,index++,k);
}
}
for (int k = index;k<=i;k++){
arr[k] *=-1;
}
//这一段我们处理负数部分,先将值为int类型中的最小值移动到最前端,将其余的部分都×-1使他
们变成正数
bucketSort(arr,index,i);//变成正数的负数组排序
for (int k = index;k<=i;k++){
arr[k]*=-1;
}
//将所有变成正数的负数重现变成负数
int temp = 0;
for (int k = index;k<(i-index+1)/2+index;k++){
swap(arr,k,i-temp);
temp++;
}
//整体交换负数组的元素
if (j>i){
bucketSort(arr,i+1,j);//排序正数组
}
}