十大排序算法之Java实现

选择排序

首先找到数组中最小的那个元素,其次,将它和数组的第一个元素交换位置(如果第一个元素就是最小元素那么它就和自己交换)。其次,在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。如此往复,直到将整个数组排序。这种方法我们称之为选择排序

public class SelectSort {
    public static int[] selectSort(int[] a) {
        int n = a.length;
        for(int i = 0; i < n; i++) {g
            int min = i;
            for(int j = i+1; j < n; j++) {
                if(a[min] > a[j]) min = j;
            }

             // 交换
             int temp = a[i];
             a[i] = a[min];
             a[min] = temp;
        }
        return a;
    }
}

1、 时间复杂度:O(n2)

2、空间复杂度:O(1)

3、非稳定排序

4、原地排序

插入排序

插入排序的一个简单理解就是我们平时在打牌时,将每一张牌插入到其他已经有序的牌中的适当位置。当我们给无序的数组做排序时,为了插入元素,我们需要先腾出空间,将其余所有元素在插入之前都向右移动一位,这种算法我们称之为插入排序

public class InsertSort {
    public static int[] insertSort(int[] arr) {
        if(arr == null || arr.length < 2)
            return arr;

        int n = arr.length;
        for(int i = 1; i < n; i++) {
            int temp = arr[i];
            int k = i - 1;

            while(k >= 0 && arr[k] > temp)
                k--;
            // 腾出位置插进去,要插的位置是k+1
            for(int j = i; j > k; j--)
                arr[j] = arr[j-1];
            
            // 插进去
            arr[k+1] = temp;
        }
        return arr;
    }
}

1、 时间复杂度:O(n2)

2、空间复杂度:O(1)

3、稳定排序

4、原地排序

冒泡排序

把第一个元素与第二个元素相比较,如果第一个比第二个大,则交换他们的位置。接着继续比较第二个与第三个元素,如果第二个比第三个大,则交换他们的位置...

非优化版本

public class BubbleSort {
    public static int[] bubbleSort(int[] arr) {
        if(arr == null || arr.length < 2) {
            return arr;
        }
        int n = arr.length;
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
                int t = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = t;
            }
        }
        return arr;
    }
}

 1、 时间复杂度:O(n2)

2、空间复杂度:O(1)

3、稳定排序

4、原地排序

优化版本

 假如从开始的第一对到结尾的最后一对,相邻的元素之间都没有发生交换的操作,这意味着右边的元素总是大于等于左边的元素,此时的数组已经是有序的了,我们无需再对剩余的元素重复比较下去了。基于这点我们可以对上述代码做如下优化。

public class BubbleSort {
    public static int[] bubbleSort(int[] arr) {
        if(arr == null || arr.length < 2) {
            return arr;
        }
        int n = arr.length;
        for(int i = 0; i < n; i++) {
            boolean flag = true;
            for(int j = 0; j < n; j++) {
                if(arr[j+1] < arr[j]) {
                    flag = false;
                    int t = arr[j+1];
                    arr[j+1] = t;
                }
            }
            // 遍历一遍下来看是否发生过位置交换
            if(false)
                break;
        }
        return arr;
    }
}

 

希尔排序

 

 1 public class ShellSort {
 2     public static int[] shellSort(int arr[]) {
 3         if(arr == null || arr.length < 2) return arr;
 4         int n = arr.length;
 5         // 对每组间隔为h的分组进行排序,刚开始h = n / 2;
 6         for(int h = n/2; h > 0; h /= 2) {
 7             int i = h; i < h; i++) {
 8                 // 将arr[i]插入到所在分组的正确位置上
 9                 insert(arr, h, i);
10             }
11         }
12         return arr;
13     }
14     
15     /**
16      * 将arr[i]插入到所在分组的正确位置上
17      * arr[i]所在分组为 ... arr[i-2*h], arr[i-h], arr[i+h] ...
18     */
19     private static void insertI(int[] arr, int h, int i) {
20         int temp = arr[i];
21         int k;
22         for(k = i-h; k > 0 && temp < arr[k]; k -= h) {
23             arr[k+h] = arr[k];
24         }
25         arr[k+h] = temp;
26     }
27 }

1、 时间复杂度:O(n2)

2、空间复杂度:O(1)

3、非稳定排序

4、原地排序

归并排序

递归式归并排序

 1 public class MergeSort {
 2     public static int[] mergeSort(int[] arr, int left, int right) {
 3         // 如果left==right,表示数组只有一个元素,则不用递归排序
 4         if(left < right) {
 5             // 把大的数组分隔成两个数组
 6             int mid = (left + right) / 2;
 7             // 对左半部分进行排序
 8             arr = mergeSort(arr, left, mid);
 9             // 对右半部分进行排序
10             arr = mergeSort(arr, mid+1, right);
11             // 进行合并
12             merge(arr, left, mid, right);
13         }
14         return arr;
15     }
16 
17     // 合并函数,把两个有序的数组合并起来
18     // arr[left..mif]表示一个数组,arr[mid+1..right]表示一个数组
19     private static void merge(int[] arr, int left, int mid, int right) {
20         // 先用一个临时数组把他们合并汇总起来
21         int[] a = new int[right - left + 1];
22         int i = left;
23         int j = mid + 1;
24         int k = 0;
25         while(i <= mid && j <= right) {
26             if(arr[i] < arr[j] {
27                 a[k++] = arr[i++];
28             } else {
29                 a[k++] = arr[j++];
30             }
31         }
32         while(i <= mid) a[k++] = arr[i++];
33         while(j <= right) a[k++] = arr[j++];
34         // 把临时数组复制到原数组
35         for(i = 0; i < k; i++) {
36             arr[left++] = a[i];
37         }
38     }
39 }

1、 时间复杂度:O(n2)

2、空间复杂度:O(n)

3、稳定排序

4、原地排序

非递归式归并排序

 1 public class MergeSort {
 2     // 非递归式的归并排序
 3     public static int[] mergeSort(int[] arr) {
 4         // 子数组的大小分别是1, 2, 4, 8. ...
 5         // 刚开始合并的数组大小是1,接着是2,接着4,...
 6         for(int i = 1; i < n; i += i) {
 7             // 进行数组进行划分
 8             int left = 0;
 9             int mid = left + i - 1;
10             int right = mid + i;
11 
12             // 进行合并,对数组大小为i的数组进行两两合并
13             while(right < n) {
14                 // 合并函数和递归式的合并函数一样
15                 merge(arr, left, mid, right);
16                 left = right + 1;
17                mid = left + i - 1;
18                right = mid + 1;
19             }
20 
21             // 还有一些被遗漏的数组的合并,因为不可能每个数组的大小都刚好为1
22             if(left < n && mid < n) {
23                 merge(arr, left, mid, n - 1);
24             }
25         }
26         return arr;
27     }
28 }

 

快速排序

 我们先从数组中选择一个元素作为中轴元素,然后把数组中所有小于中轴元素的元素放在其左边,所有大于或等于中轴元素的元素放在其右边,显然,此时中轴元素所处的位置是有序。也就是说,我们无需再移动中轴元素的位置。

 

然后从中轴元素那里开始把大的数组切割成两个小的数组(两个数组都不包含中轴元素),接着我们通过递归的方式,让中轴元素左边的数组和右边的数组也重复同样的操作,知道数组的大小为1,此时每个元素都处于有序的位置。

 1 public class QuickSort {
 2     public static int[] quickSort(int[] arr, int left, int right) {
 3         if(left < right) {
 4            // 获取中轴元素所处的位置
 5            int mid = partition(arr, left, right);
 6 
 7            // 进行分割
 8            arr = quickSort(arr, left, mid - 1);
 9            arr = quickSort(arr, mid + 1, right);
10         }
11         return arr;
12     }
13 
14     private static int partition(int[] arr, int left, int right) {
15         // 选取中轴元素
16         int pivot = arr[left];
17         int i = left + 1;
18         int j = right;
19         while(true) {
20             // 向右找到第一个小于等于pivot的元素位置
21             while(i <= j && arr[i] <= pivot) i++;
22             // 向左找到第一个大于的等于pivot的元素位置
23             while(i <= j && arr[j] >= pivot) j--;
24             if(i >= j)
25                 break;
26             // 交换两个元素的位置,使得左边的元素不大于pivot,右边的不小于pivot
27             int temp = arr[i];
28             arr[i] = arr[j];
29             arr[j] = temp;
30         }
31         arr[left] = arr[j];
32         // 使中轴元素处于有序的位置
33         arr[j] = pivot;
34         return j;
35     }
36 }

1、 时间复杂度:O(nlogn)

2、空间复杂度:O(logn)

3、非稳定排序

4、原地排序

堆排序

堆的特点就是堆顶的元素是一个最值,大顶堆的堆顶是最大值,小堆顶则是最小值。

堆排序就是把堆顶的元素与最后一个元素交换,交换之后就破坏了堆的特性。我们再把堆中剩余的元素再次构成一个大顶堆,然后再把堆顶元素与最后第二个元素交换....如此往复下去,等到剩余的元素只有一个的时候,此时的数组就是有序 的了。

 1 public class Head {
 2     // 堆排序
 3     public static int[] headSort(int[] arr) {
 4 
 5         int n = arr.length;
 6         // 构建⼤大顶堆
 7         for (int i = (n - 2) / 2; i >= 0; i--) {
 8             downAdjust(arr, i, n - 1);
 9         }
10         
11         // 进⾏行行堆排序
12         for (int i = n - 1; i >= 1; i--) {
13             // 把堆顶元素与最后⼀一个元素交换
14             int temp = arr[i];
15             arr[i] = arr[0];
16             arr[0] = temp;
17             // 把打乱的堆进⾏行行调整,恢复堆的特性
18             downAdjust(arr, 0, i - 1);
19         }
20 
21         return arr;
22     }
23 
24     // 下沉操作
25     public static void downAdjust(int[] arr, int parent, int n) {
26         // 临时保存要下沉的元素
27         int temp = arr[parent];
28         // 定位左孩⼦子节点的位置
29         int child = 2 * parent + 1;
30         // 开始下沉
31         while (child <= n) {
32             // 如果右孩⼦子节点⽐比左孩⼦子⼤大,则定位到右孩⼦子
33             if(child + 1 <= n && arr[child] < arr[child + 1])
34                 child++;
35             // 如果孩⼦子节点⼩小于或等于⽗父节点,则下沉结束
36             if (arr[child] <= temp ) break;
37             // ⽗父节点进⾏行行下沉
38             arr[parent] = arr[child];
39             parent = child;
40             child = 2 * parent + 1;
41         }
42         arr[parent] = temp;
43     }
44 }

 

 1、 时间复杂度:O(n2)

2、空间复杂度:O(1)

3、非稳定排序

4、原地排序

基数排序

 

非优化版本

优化版本

桶排序

 1 public class BucketSort {
 2     public static int[] BucketSort(int[] arr) {
 3         if(arr == null || arr.length < 2) return arr;
 4         int n = arr.length;
 5         int max = arr[0];
 6         int min = arr[0];
 7 
 8         // 寻找数组的最⼤大值与最⼩小值
 9         for (int i = 1; i < n; i++) {
10             if(min > arr[i])
11                 min = arr[i];
12             if(max < arr[i])
13                 max = arr[i];
14         }
15 
16         // 和优化版本的计数排序⼀一样,弄弄⼀一个⼤大⼩小为 min 的偏移值
17         int d = max - min;
18         // 创建 d / 5 + 1 个桶,第 i 桶存放 5*i ~ 5*i+5-1范围的数
19         int bucketNum = d / 5 + 1;
20         ArrayList<LinkedList<Integer>> bucketList = new ArrayList<>(bucketNum);
21 
22         // 初始化桶
23         for (int i = 0; i < bucketNum; i++) {
24             bucketList.add(new LinkedList<Integer>());
25         }
26 
27         // 遍历原数组,将每个元素放⼊入桶中
28         for (int i = 0; i < n; i++) {
29             bucketList.get((arr[i]-min)/d).add(arr[i] - min);
30         }
31 
32         // 对桶内的元素进⾏行行排序,我这⾥里里采⽤用系统⾃自带的排序⼯工具
33         for (int i = 0; i < bucketNum; i++) {
34             Collections.sort(bucketList.get(i));
35         }
36 
37         // 把每个桶排序好的数据进⾏行行合并汇总放回原数组
38         int k = 0;
39         for (int i = 0; i < bucketNum; i++) {
40             for (Integer t : bucketList.get(i)) {
41                 arr[k++] = t + min;
42             }
43         }
44         return arr;
45     }
46 }

 

性质:

1、时间复杂度:O(n+k)

2、空间复杂度:O(n+k)

3、稳定排序

4、⾮非原地排序

注:k 表示桶的个数

 

posted @ 2020-12-21 01:10  seeyoumeet  阅读(115)  评论(0编辑  收藏  举报