排序依赖的方法和数据结构
1 交换和比较
1) 使用一个临时元素保存中间值,空间复杂度是O(1)。
1
2
3
4
5
6
7
private void swap(int[] nums,int i ,int j){
if(i == j) return;
if(nums[i] == nums[j]) return;
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
2) 不使用临时元素,但需要注意当i==j时会出错,所以需要先处理这种情况。 当i==j 或者nums[i]==nums[j]时无需交换,快速返回
1
2
3
4
5
6
7
private void swap(int[] nums,int i, int j){
if(i == j) return;
if(nums[i] == nums[j]) return;
nums[i] ^= nums[j];
nums[j] ^= nums[i];
nums[i] ^= nums[j];
}
3) 比较大小
1
2
3
private boolean less(int[] a, int i, int j){
return a[i] < a[j];
}
2 链表结构
1
2
3
4
5
6
public class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
ListNode(){}
}
2 选择排序
每次从剩余元素选择一个最小的,排在当前位置.
1
2
3
4
5
6
7
8
9
10
11
public void selectionSort(int[] nums){
if(nums == null || nums.length <= 1) return;
int n = nums.length;
for(int i = 0; i < n; i++){
int min = i;
for(int j = i+1; j < n; j++){
if(less(nums,j,min)) min = j;
}
swap(nums,i,min);
}
}
3 插入排序
1 数组
每次都把一个元素插入到已经有序的序列里。
1
2
3
4
5
6
7
8
9
10
public void insertionSort(int[] nums){
if(nums == null || nums.length <= 1) return;
int n = nums.length;
//i之前的已经有序
for(int i = 1; i < n ; i++){
for(int j = i; j >= 1 && less(nums,j,j-1); j--){
swap(nums,j,j-1);
}
}
}
2 链表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public ListNode insertionSort(ListNode head){
if(head == null || head.next == null) return head;
ListNode dummyHead = new ListNode();
ListNode pre;
while(head != null){
pre = dummyHead;
while(pre.next != null && pre.next.val < head.val){
pre = pre.next;
}
ListNode tmp = head.next;
head.next = pre.next;
pre.next = head;
head = tmp;
}
return dummyHead.next;
}
4 希尔排序
选择的序列h = 1,4,13,40....
希尔排序可以这样理解,每次排序间隔为h的元素。
比如
0 1 2 3 4 5 6 7 8 9 10
h = 4 时:排序0 4 8,1 5 9, 2 6 10, 3 7
h = 1 时 排序 0 1 2 3 4 5 6 7 8 9 10
代码可以更清晰的表现出希尔排序和插入排序的关系。
仔细比较可以看出,插入排序仅仅是希尔排序当h=1时的特殊情况。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void shellSort(int[] nums){
if(nums == null || nums.length <= 1) return;
int n = nums.length;
int h = 1;
while(h < n / 3) h = 3 * h + 1;
while(h >= 1){
for(int i = h; i < n; i++){
for(int j = i; j >= h && less(nums,j,j-h); j-=h){
swap(nums,j,j-h);
}
}
h = h / 3;
}
}
5 快速排序
每趟排序时选定一个元素,将小于该元素的放在元素左边,大于该元素的放在右边,然后 递归的处理左右两边。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void quickSort(int[] nums){
if(nums == null || nums.length <= 1) return;
int hi = nums.length - 1;
quickSort(nums,0,hi);
}
private void quickSort(int[] nums,int lo,int hi){
if(lo >= hi) return;
int mid = lo + (hi - lo) / 2;
int j = partition(nums,lo,hi);
quickSort(nums,lo,j-1);
quickSort(nums,j+1,hi);
}
private int partition(int[] nums,int lo,int hi){
int i = lo;
int j = hi+1;
int v = nums[lo];
while(true){
while(less(nums,++i,lo)) if(i == hi) break;
while(less(nums,lo,--j)) if(j == lo) break;
if(i >= j) break;
swap(nums,i,j);
}
swap(nums,lo,j);
return j;
}
6 归并排序
1 数组
归并排序的基本思路就是先排序左半部分,在排序有半部分,然后将两部分合并。
下面代码
mergeSort(nums,lo,mid);
mergeSort(nums,mid+1,hi);
merge(nums,lo,mid,hi);
就是这个思路。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void mergeSort(int[] nums){
if(nums == null || nums.length <= 1) return;
//aux是辅助数组
aux = new int[nums.length];
mergeSort(nums,0,nums.length-1);
}
private void mergeSort(int[] nums,int lo, int hi){
if(lo >= hi) return;
int mid = lo + (hi - lo) / 2;
mergeSort(nums,lo,mid);
mergeSort(nums,mid+1,hi);
merge(nums,lo,mid,hi);
}
private void merge(int[] nums,int lo,int mid,int hi){
//将nums[lo..mid]和nums[mid+1,hi]合并
int i = lo;
int j = mid + 1;
for(int k = lo; k <= hi; k++){
aux[k] = nums[k];
}
for(int k = lo; k <= hi; k++){
if(i > mid) nums[k] = aux[j++];
else if(j > hi) nums[k] = aux[i++];
else if(less(aux,i,j)) nums[k] = aux[i++];
else nums[k] = aux[j++];
}
}
2 链表
其中看下找链表中点的方法findMid(),设定两个指针,一个slow,一个fast, slow每次移动一步,fast一次移动两步,当fast.next==null 或者fast.next.next==null时,此时slow指向的即是链表中点.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public ListNode mergeSort(ListNode head){
if(head == null || head.next == null) return head;
ListNode mid = findMid(head);
ListNode r = mid.next;
mid.next = null;
ListNode left = mergeSort(head);
ListNode right = mergeSort(r);
return merge(left,right);
}
private ListNode merge(ListNode left,ListNode right){
ListNode dummyHead = new ListNode();
ListNode cur = dummyHead;
while(left != null && right != null){
if(left.val < right.val){
cur.next = left;
left = left.next;
}else{
cur.next = right;
right = right.next;
}
cur = cur.next;
}
cur.next = (left == null) ? right : left;
return dummyHead.next;
}
private ListNode findMid(ListNode head){
ListNode slow,fast;
slow = fast = head;
while(fast.next != null && fast.next.next != null){
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
3 自底向上的归并排序
1
2
3
4
5
6
7
8
9
10
public void mergeSortBU(int[] nums){
if(nums == null || nums.length <= 1) return;
int n = nums.length;
aux = new int[n];
for(int sz = 1; sz < n; sz += sz){
for(int lo = 0; lo < n - sz; lo += sz + sz){
merge(nums,lo,lo+sz-1,Math.min(lo+sz+sz-1,n-1));
}
}
}
7 堆排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void heapSort(int[] nums){
if(nums == null || nums.length <= 1) return;
int hi = nums.length - 1;
for(int k = hi / 2; k >= 0; k--){
sink(nums,k,hi);
}
while(hi > 0){
swap(nums,0,hi--);
sink(nums,0,hi);
}
}
private void sink(int[] nums,int i,int hi){
while(2i+1 <= hi){
int j = 2i+1;
if(j < hi && less(nums,j,j+1)) j = j + 1;
if(less(nums,j,i)) break;
swap(nums,i,j);
i = j;
}
}
8 冒泡排序
冒泡排序时比较简单的入门级排序算法。
1
2
3
4
5
6
7
8
9
10
public void bubbleSort(int[] nums){
if(nums == null || nums.length <= 1) return;
for(int i = 0; i < nums.length; i++){
for (int j = 0; j < nums.length-1-i ; j++) {
if(!less(nums,j,j+1)){
swap(nums,j,j+1);
}
}
}
}
以上都是基于比较的排序,下面的是三种非比较算法。(后续补完)
9计数排序
顾名思义,通过计数来排列元素的位置。通俗来讲,就是通过计算其中元素比多少个数大来排列位置, 虽然说是非比较算法,但个人感觉只是没有明显的用到比较,但在构造数组的过程中已经隐含了大小的概念。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public int[] countSort(int[] nums,int max){
if(nums == null || nums.length <= 1) return;
int n = nums.length;
//计数数组
int[] c = new int[max+1];
//存储数组
int[] r = new int[n];
//初始化计数数组
for(int i = 0; i < n; i++){
c[nums[i]]++;
}
//计算
for(int i = 1; i < n; i++){
c[i] = c[i] + c[i-1];
}
//排列元素
for(int i = n-1; i >= 0; i--){
r[--c[nums[i]]] = nums[i];
}
return r;
}
2基数排序
其中用到了计数排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public void radixSort(int[] nums,int radix,int digits){
if(nums == null || nums.length <= 1) return;
int n = nums.length;
//暂存中间排序结果
int[] tmp = new int[n];
//计数排序中的count数组
int[] count = new int[radix];
int divide = 1;
for(int i = 0; i < digits; i++){
for(int j = 0; j < n; j++){
tmp[j] = nums[j];
}
for(int j = 0; j < radix ; j++){
count[j] = 0;
}
for(int j = 0; j < n; j++){
int t = (tmp[j] / divide) % radix;
count[t]++;
}
for(int j = 1; j < radix ; j++){
count[j] = count[j] + count[j-1];
}
for(int j = n-1; j >= 0; j--){
int t = (nums[j] / divide) % radix;
tmp[--count[t]] = nums[j];
}
divide = divide * radix;
}
System.err.println(Arrays.toString(tmp));
}
桶排序
这个让我想起我收拾一个小图书馆的书架的事情。当时有1000多本的图书,要根据类型和序号进行排序,我首先看了下书籍大概分块放的,然后就闷头开始做。一会就发现这种方法行不通,然后就想到可以先把范围变小,我先腾出一块空地方,将图书按类别放在对应的位置,然后再按序号进行排序。桶排序也差不多是这个意思。 比如对于[0-99)的数据,可以分成10个桶[0-10),[10-20),[20-30),[30-40),[40-50),[50-60),[60-70),[70-80),[80-90),[90-100)。然后将待排序的数字扔到对应的桶里,桶内排序,然后依次取出即可。我看网上的桶排序的有的用二维数组来做,感觉比较麻烦,用链表来做就很简单了,也很明了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public void bucketSort(int[] nums){
if(nums == null || nums.length <= 1) return;
int n = nums.length;
bucketNode[] bucket = new bucketNode[100];
for(int i = 0; i < bucket.length; i++){
bucketNode node = new bucketNode();
bucket[i]= node;
}
for(int i = 0; i < n; i++){
int tmp = nums[i] / 10;
bucketNode node = new bucketNode();
node.val = nums[i];
bucketNode pre = bucket[tmp];
while(pre.next != null && pre.next.val < nums[i]){
pre = pre.next;
}
node.next = pre.next;
pre.next = node;
}
for(int i = 0; i < bucket.length; i++){
bucketNode tmp = bucket[i].next;
while(tmp != null){
System.err.println(tmp.val);
tmp = tmp.next;
}
}
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">bucketNode</span><span class="o">{</span>
<span class="kt">int</span> <span class="n">val</span><span class="o">;</span>
<span class="n">bucketNode</span> <span class="n">next</span><span class="o">;</span>
<span class="o">}</span><span class="w">
排序算法比较
名称 | 数据对象 | 稳定性 |
|
空间复杂度 | 简要描述 | ||||||
---|---|---|---|---|---|---|---|---|---|---|---|
冒泡排序 | 数组 | 稳定 |
|
O(1) | (无序区,有序区)。从无序区通过交换找出最大元素放到有序区前端 | ||||||
选择排序 |
|
|
|
O(1) | (有序区,无序区)。在无序区里找一个最小的元素跟在有序区的后面。对数组:比较得多,换得少。 | ||||||
插入排序 | 数组 链表 | 稳定 | O(n^2) | O(1) | (有序区,无序区)。把无序区的第一个元素插入到有序区的合适的位置。对数组:比较得少,换得多。 | ||||||
堆排序 | 数组 | 不稳定 | O(nlogn) | O(1) | (最大堆,有序区)。从堆顶把根卸出来放在有序区之前,再恢复堆。 | ||||||
归并排序 |
|
稳定 | O(nlogn) |
|
把数据分为两段,从两段中逐个选最小的元素移入新数据段的末尾。可从上到下或从下到上进行。 | ||||||
快速排序 | 数组 | 不稳定 |
|
O(logn),O(n) | (小数,枢纽元,大数) | ||||||
希尔排序 | 数组 | 不稳定 |
|
O(1) | 每一轮按照事先决定的间隔进行插入排序,间隔会依次缩小,最后一次一定要是1。 | ||||||
计数排序 | 数组 链表 | 稳定 | O(n+m) | O(n+m) | 统计小于等于该元素值的元素的个数i,于是该元素就放在目标数组的索引i位(i≥0) | ||||||
桶排序 | 数组链表 | 稳定 | O(n) | O(m) | 将值为i的元素放入i号桶,桶内进行排序,最后依次把桶里的元素倒出来。 | ||||||
基数排序 | 数组 链表 | 稳定 |
|
一种多关键字的排序算法,可用桶排序实现。 |