思路:
- 已知待排序数组arr。创建桶数组
ListNode[] buckets(arr.length)
,定义每个桶容纳的数据区间长度为bucketLEN = (max - min + 1) / arr.length + 1
, 对于第i
区间数据区间为[bucketLEN * i, (i+1)*bucketLEN-1)
- 对每个桶进行排序,即对每个单链表进行排序(
leetcode 148
) - 从小到大遍历桶,将数据合并到 arr 中
时间复杂度:
分析参考:https://blog.csdn.net/zihonggege/article/details/104781491/
性能分析:
经过上面的分析,你应该懂得了桶排序的思想。不过你可能会有一个疑问:每个桶内再用其他的排序算法进行排序(比如快排),这样子时间复杂度不还是 O(nlogn) 吗?请看下面这段分析。
如果要排序的数据有 n 个,我们把它们分在 m 个桶中,这样每个桶里的数据就是
k=n/m
。每个桶内排序的时间复杂度就为O(k*logk)
。m 个桶就是m * O(k * logk) = m * O((n / m) * log(n / m))=O(n * log(n / m))
。当桶的个数 m 接近数据个数 n 时,log(n/m)就是一个较小的常数,所以时间复杂度接近 O(n)。
public class BucketSort {
public static void main(String[] args) {
// test cases
int[][] nums = {{31, 33, 27, 15, 42, 11, 40, 5, 19, 21}, {98, 34, 100, 36, 44, 64, 3, 99, 59},
{66, 62, 4, 65, 49, 71, 71, 24, 12}, {14, 3, 58, 23, 12, 66, 11, 45, 36},
{55, 64, 35, 24, 85, 73, 33, 85, 46}, {94, 76, 23, 36, 57, 26, 8, 92, 17},
{85, 68, 52, 34, 53, 93, 4, 37, 34}, {70, 9, 15, 42, 31, 16, 72, 61, 62},
{11, 38, 34, 21, 81, 9, 45, 68, 11}, {20, 83, 27, 6, 69, 26, 5, 31, 8},
{74, 97, 11, 60, 1, 68, 14, 27, 46}, {5, 8, 7, -5, 9, 2, 0, 8, 3, 20}};
// test each case by bucket sort
for (int[] num : nums) {
System.out.println("before: " + Arrays.toString(num));
bucketSort(num);
System.out.println("after: " + Arrays.toString(num));
System.out.println("----------------");
}
}
public static void bucketSort(int[] arr) {
if (arr.length == 0) return;
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for (int n : arr) {
min = Math.min(min, n);
max = Math.max(max, n);
}
// n-th bucket ===>> [n * bucketLEN, (n+1)*bucketLEN - 1)
int bucketLEN = (max - min + 1) / arr.length + 1;
// number of bucket
ListNode[] buckets = new ListNode[arr.length];
// iterate over source array, put them into buckets array
int index;
for (int n : arr) {
// find target bucket
index = (n - min) / bucketLEN;
// put element into buckets[index]
if (index >= arr.length)
throw new RuntimeException("bad buckets!");
else if (buckets[index] == null) {
buckets[index] = new ListNode(n);
} else {
// put new node at the 0 position of the bucket
buckets[index] = new ListNode(n, buckets[index]);
}
}
// sort every bucket separately
for (int i = 0; i < arr.length; i++) {
// list merge sort
buckets[i] = sortList(buckets[i]);
}
// combine buckets into array
index = 0;
ListNode cur;
for (int i = 0; i < arr.length; i++) {
cur = buckets[i];
while (cur != null) {
arr[index++] = cur.val;
cur = cur.next;
}
}
}
static class ListNode {
int val;
ListNode next;
ListNode() {
}
ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
// prepare before sort
public static ListNode sortList(ListNode head) {
// use dummy node, more easily
HEAD_DUMMY = new ListNode(-1);
TAIL_DUMMY = new ListNode(-1);
sort(head); // do sort list
return HEAD_DUMMY.next;
}
//dummy node
static ListNode HEAD_DUMMY, TAIL_DUMMY;
/**
* merge sort on list.
* use fast-slow-pointers method to positioning middle node in the list.
* fast-slow-pointers method:
* 1. define two pointers, iterating over the list from front to end
* 2. fast pointer moves two step while slow pointer moves one step every time util faster can't move,
* 3. in the end, slow pointer points to the middle of the list.
* <p>
* finally: headDummy.next is the real head, and tailDummy.next is the real tail.
*/
public static void sort(ListNode head) {
// base cases
if (head == null || head.next == null)
HEAD_DUMMY.next = TAIL_DUMMY.next = head;//when number of list node = 0 or 1
else if (head.next.next == null) {//when number of list node = 2
if (head.val > head.next.val) {
head.next.next = head;
head = head.next;
head.next.next = null;
}
HEAD_DUMMY.next = head;
TAIL_DUMMY.next = head.next;
} else {//when number of list node >= 3, like 3->4->5...
// split list by fast-slow-pointer method
ListNode fast = head.next.next, slow = head.next;
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
// ===> head->..->slow->null, tmp->...->null
ListNode tail, tmp = slow.next;
slow.next = null;
//left half list
sort(head);
head = HEAD_DUMMY.next;
tail = TAIL_DUMMY.next;
//right half list
sort(tmp);
//merge left and right part of list
HEAD_DUMMY.next = mergeTwoLists(head, HEAD_DUMMY.next);
TAIL_DUMMY.next = (tail.val > TAIL_DUMMY.val) ? tail : TAIL_DUMMY;
}
}
// merge two list
public static ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode head, cur = new ListNode(-1);
head = cur;
while (true) {
if (l1 == null || l2 == null) {
cur.next = (l1 == null) ? l2 : l1;
break;
} else {
if (l1.val < l2.val) {
cur.next = l1;
l1 = l1.next;
} else {
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
}
return head.next;
}
}