十种经典的排序算法可以分为两大类:
冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
Python:
def bubbleSort(arr):
for i in range(1, len(arr)):
for j in range(0, len(arr)-i):
if arr[j] > arr[j+1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arr
JAVA:
public static int[] bubbleSort(int[] arr) {
if (arr.length == 0)
return arr;
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length - 1 - i; j++)
if (arr[j + 1] < arr[j]) {
int temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
return arr;
}
C++:
void bubbleSort(vector<int>& arr) {
if (arr.size() == 0)
return ;
for (int i = 0; i < arr.size(); i++) {
for (int j = 0; j < arr.size() - 1 - i; j++)
if (arr[j + 1] < arr[j]) {
int temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
return ;
}
当输入的数据已经是正序时,算法最快;当输入的数据是反序时,算法最慢。
最佳情况 | 最差情况 | 平均情况 |
---|---|---|
T ( n ) = O ( n ) T(n)=O(n) T(n)=O(n) | T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2) | T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2) |
选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。
Python:
def selectionSort(arr):
for i in range(len(arr) - 1):
# 记录最小数的索引
minIndex = i
for j in range(i + 1, len(arr)):
if arr[j] < arr[minIndex]:
minIndex = j
# i 不是最小数时,将 i 和最小数进行交换
if i != minIndex:
arr[i], arr[minIndex] = arr[minIndex], arr[i]
return arr
JAVA:
public static int[] selectionSort(int[] arr) {
if (arr.length == 0)
return arr;
for (int i = 0; i < arr.length; i++) {
int minIndex = i;
for (int j = i; j < arr.length; j++) {
if (arr[j] < arr[minIndex]) //找到最小的数
minIndex = j; //将最小数的索引保存
}
int temp = arr[minIndex];
arr[minIndex] = arr[i];
arr[i] = temp;
}
return arr;
}
C++:
void selectionSort(vector<int>& arr) {
if (arr.size() == 0)
return ;
for (int i = 0; i < arr.size(); i++) {
int minIndex = i;
for (int j = i; j < arr.size(); j++) {
if (arr[j] < arr[minIndex]) //找到最小的数
minIndex = j; //将最小数的索引保存
}
int temp = arr[minIndex];
arr[minIndex] = arr[i];
arr[i] = temp;
}
return ;
}
最佳情况 | 最差情况 | 平均情况 |
---|---|---|
T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2) | T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2) | T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2) |
插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。
Python:
def insertionSort(arr):
for i in range(len(arr)):
preIndex = i-1
current = arr[i]
while preIndex >= 0 and arr[preIndex] > current:
arr[preIndex+1] = arr[preIndex]
preIndex-=1
arr[preIndex+1] = current
return arr
JAVA:
public static int[] insertionSort(int[] arr) {
if (arr.length == 0)
return arr;
int current;
for (int i = 0; i < arr.length - 1; i++) {
current = arr[i + 1];
int preIndex = i;
while (preIndex >= 0 && current < arr[preIndex]) {
arr[preIndex + 1] = arr[preIndex];
preIndex--;
}
arr[preIndex + 1] = current;
}
return arr;
}
C++:
void insertionSort(vector<int>& arr) {
if (arr.size() == 0)
return ;
int current;
for (int i = 0; i < arr.size() - 1; i++) {
current = arr[i + 1];
int preIndex = i;
while (preIndex >= 0 && current < arr[preIndex]) {
arr[preIndex + 1] = arr[preIndex];
preIndex--;
}
arr[preIndex + 1] = current;
}
return ;
}
最佳情况 | 最差情况 | 平均情况 |
---|---|---|
T ( n ) = O ( n ) T(n)=O(n) T(n)=O(n) | T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2) | T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2) |
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
Python:
def shellSort(arr):
import math
gap=1
while(gap < len(arr)/3):
gap = gap*3+1
while gap > 0:
for i in range(gap,len(arr)):
temp = arr[i]
j = i-gap
while j >=0 and arr[j] > temp:
arr[j+gap]=arr[j]
j-=gap
arr[j+gap] = temp
gap = math.floor(gap/3)
return arr
}
JAVA:
public static int[] ShellSort(int[] arr) {
int len = arr.length;
int temp, gap = len / 2;
while (gap > 0) {
for (int i = gap; i < len; i++) {
temp = arr[i];
int preIndex = i - gap;
while (preIndex >= 0 && arr[preIndex] > temp) {
arr[preIndex + gap] = arr[preIndex];
preIndex -= gap;
}
arr[preIndex + gap] = temp;
}
gap /= 2;
}
return arr;
}
C++:
void ShellSort(vector<int>& arr) {
int len = arr.size();
int temp, gap = len / 2;
while (gap > 0) {
for (int i = gap; i < len; i++) {
temp = arr[i];
int preIndex = i - gap;
while (preIndex >= 0 && arr[preIndex] > temp) {
arr[preIndex + gap] = arr[preIndex];
preIndex -= gap;
}
arr[preIndex + gap] = temp;
}
gap /= 2;
}
return ;
}
最佳情况 | 最差情况 | 平均情况 |
---|---|---|
T ( n ) = O ( n l o g 2 ( n ) ) T(n)=O(nlog_2(n)) T(n)=O(nlog2(n)) | T ( n ) = O ( n l o g 2 ( n ) ) T(n)=O(nlog_2(n)) T(n)=O(nlog2(n)) | T ( n ) = O ( n l o g 2 ( n ) ) T(n)=O(nlog_2(n)) T(n)=O(nlog2(n)) |
归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:
和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 O(nlogn) 的时间复杂度。代价是需要额外的内存空间。
Python:
def mergeSort(arr):
import math
if(len(arr)<2):
return arr
middle = math.floor(len(arr)/2)
left, right = arr[0:middle], arr[middle:]
return merge(mergeSort(left), mergeSort(right))
def merge(left,right):
result = []
while left and right:
if left[0] <= right[0]:
result.append(left.pop(0));
else:
result.append(right.pop(0));
while left:
result.append(left.pop(0));
while right:
result.append(right.pop(0));
return result
JAVA:
public static int[] MergeSort(int[] arr) {
if (arr.length < 2)
return arr;
int mid = arr.length / 2;
int[] left = Arrays.copyOfRange(arr, 0, mid);
int[] right = Arrays.copyOfRange(arr, mid, arr.length);
return merge(MergeSort(left), MergeSort(right));
}
public static int[] merge(int[] left, int[] right) {
int[] result = new int[left.length + right.length];
for (int index = 0, i = 0, j = 0; index < result.length; index++) {
if (i >= left.length)
result[index] = right[j++];
else if (j >= right.length)
result[index] = left[i++];
else if (left[i] > right[j])
result[index] = right[j++];
else
result[index] = left[i++];
}
return result;
}
C++:
vector<int> MergeSort(vector<int> arr) {
if (arr.size() < 2)
return ;
int mid = arr.size() / 2;
vector<int> left(arr.begin(), arr.begin() + mid);
vector<int> right(arr.begin() + mid, arr.end());
return merge(MergeSort(left), MergeSort(right));
}
vector<int> merge(vector<int>&& left, vector<int>&& right) {
vector<int> result(left.size() + right.size());
for (int index = 0, i = 0, j = 0; index < result.size(); index++) {
if (i >= left.size())
result[index] = right[j++];
else if (j >= right.size())
result[index] = left[i++];
else if (left[i] > right[j])
result[index] = right[j++];
else
result[index] = left[i++];
}
return result;
}
最佳情况 | 最差情况 | 平均情况 |
---|---|---|
T ( n ) = O ( n ) T(n)=O(n) T(n)=O(n) | T ( n ) = O ( n l o g 2 ( n ) ) T(n)=O(nlog_2(n)) T(n)=O(nlog2(n)) | T ( n ) = O ( n l o g 2 ( n ) ) T(n)=O(nlog_2(n)) T(n)=O(nlog2(n)) |
快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。
快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。
快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。
快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高!它是处理大数据最快的排序算法之一了。虽然 Worst Case 的时间复杂度达到了 O(n²),但是人家就是优秀,在大多数情况下都比平均时间复杂度为 O(n logn) 的排序算法表现要更好,可是这是为什么呢,我也不知道。好在我的强迫症又犯了,查了 N 多资料终于在《算法艺术与信息学竞赛》上找到了满意的答案:
快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。
递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
Python:
def quickSort(arr, left=None, right=None):
left = 0 if not isinstance(left,(int, float)) else left
right = len(arr)-1 if not isinstance(right,(int, float)) else right
if left < right:
partitionIndex = partition(arr, left, right)
quickSort(arr, left, partitionIndex-1)
quickSort(arr, partitionIndex+1, right)
return arr
def partition(arr, left, right):
pivot = left
index = pivot+1
i = index
while i <= right:
if arr[i] < arr[pivot]:
swap(arr, i, index)
index+=1
i+=1
swap(arr,pivot,index-1)
return index-1
def swap(arr, i, j):
arr[i], arr[j] = arr[j], arr[i]
JAVA:
public static int[] QuickSort(int[] array, int start, int end) {
if (array.length < 1 || start < 0 || end >= array.length || start > end) return null;
int smallIndex = partition(array, start, end);
if (smallIndex > start)
QuickSort(array, start, smallIndex - 1);
if (smallIndex < end)
QuickSort(array, smallIndex + 1, end);
return array;
}
public static int partition(int[] arr, int start, int end) {
int pivot = (int)(start + Math.random() * (end - start + 1));
int smallIndex = start - 1;
swap(arr, pivot, end);
for (int i = start; i <= end; i++)
if (arr[i] <= arr[end]) {
smallIndex++;
if (i > smallIndex)
swap(arr, i, smallIndex);
}
return smallIndex;
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
C++:
void QuickSort(vector<int>& array, int start, int end) {
if (array.size() < 1 || start < 0 || end >= array.size() || start > end) return ;
int smallIndex = partition(array, start, end);
if (smallIndex > start)
QuickSort(array, start, smallIndex - 1);
if (smallIndex < end)
QuickSort(array, smallIndex + 1, end);
return ;
}
int partition(vector<int>& arr, int start, int end) {
int pivot = (int)(start + rand() * (end - start + 1));
int smallIndex = start - 1;
swap(arr, pivot, end);
for (int i = start; i <= end; i++)
if (arr[i] <= arr[end]) {
smallIndex++;
if (i > smallIndex)
swap(arr, i, smallIndex);
}
return smallIndex;
}
void swap(vector<int>& arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
最佳情况 | 最差情况 | 平均情况 |
---|---|---|
T ( n ) = O ( n l o g 2 ( n ) ) T(n)=O(nlog_2(n)) T(n)=O(nlog2(n)) | T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2) | T ( n ) = O ( n l o g 2 ( n ) ) T(n)=O(nlog_2(n)) T(n)=O(nlog2(n)) |
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:
堆排序的平均时间复杂度为 Ο(nlogn)。
Python:
def buildMaxHeap(arr):
import math
for i in range(math.floor(len(arr)/2),-1,-1):
heapify(arr,i)
def heapify(arr, i):
left = 2*i+1
right = 2*i+2
largest = i
if left < arrLen and arr[left] > arr[largest]:
largest = left
if right < arrLen and arr[right] > arr[largest]:
largest = right
if largest != i:
swap(arr, i, largest)
heapify(arr, largest)
def swap(arr, i, j):
arr[i], arr[j] = arr[j], arr[i]
def heapSort(arr):
global arrLen
arrLen = len(arr)
buildMaxHeap(arr)
for i in range(len(arr)-1,0,-1):
swap(arr,0,i)
arrLen -=1
heapify(arr, 0)
return arr
JAVA:
static int len;//声明全局变量,用于记录数组array的长度;
public static int[] HeapSort(int[] arr) {
len = arr.length;
if (len < 1) return arr;
//1.构建一个最大堆
buildMaxHeap(arr);
//2.循环将堆首位(最大值)与末位交换,然后在重新调整最大堆
while (len > 0) {
swap(arr, 0, len - 1);
len--;
adjustHeap(arr, 0);
}
return arr;
}
public static void buildMaxHeap(int[] arr) {
//从最后一个非叶子节点开始向上构造最大堆
for (int i = (len / 2 - 1); i >= 0; i--) {
adjustHeap(arr, i);
}
}
public static void adjustHeap(int[] arr, int i) {
int maxIndex = i;
//如果有左子树,且左子树大于父节点,则将最大指针指向左子树
if (i * 2 < len && arr[i * 2] > arr[maxIndex])
maxIndex = i * 2;
//如果有右子树,且右子树大于父节点,则将最大指针指向右子树
if (i * 2 + 1 < len && arr[i * 2 + 1] > arr[maxIndex])
maxIndex = i * 2 + 1;
//如果父节点不是最大值,则将父节点与最大值交换,并且递归调整与父节点交换的位置。
if (maxIndex != i) {
swap(arr, maxIndex, i);
adjustHeap(arr, maxIndex);
}
}
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
Python:
def countingSort(arr, maxValue):
bucketLen = maxValue+1
bucket = [0]*bucketLen
sortedIndex =0
arrLen = len(arr)
for i in range(arrLen):
if not bucket[arr[i]]:
bucket[arr[i]]=0
bucket[arr[i]]+=1
for j in range(bucketLen):
while bucket[j]>0:
arr[sortedIndex] = j
sortedIndex+=1
bucket[j]-=1
return arr
JAVA:
public static int[] CountingSort(int[] arr) {
if (array.length == 0)
return arr;
int bias, min = arr[0], max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max)
max = arr[i];
if (arr[i] < min)
min = arr[i];
}
bias = 0 - min;
int[] bucket = new int[max - min + 1];
Arrays.fill(bucket, 0);
for (int i = 0; i < arr.length; i++) {
bucket[arr[i] + bias]++;
}
int index = 0, i = 0;
while (index < arr.length) {
if (bucket[i] != 0) {
arr[index] = i - bias;
bucket[i]--;
index++;
}
else
i++;
}
return arr;
}
当输入的元素是n 个0到k之间的整数时,它的运行时间是 O(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。
最佳情况 | 最差情况 | 平均情况 |
---|---|---|
T ( n ) = O ( n + k T(n)=O(n+k T(n)=O(n+k | T ( n ) = O ( n + k ) T(n)=O(n+k) T(n)=O(n+k) | T ( n ) = O ( n + k T(n)=O(n+k T(n)=O(n+k |
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:
同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。
注意,如果递归使用桶排序为各个桶排序,则当桶数量为1时要手动减小BucketSize增加下一循环桶的数量,否则会陷入死循环,导致内存溢出。
JAVA:
public static ArrayList<Integer> BucketSort(ArrayList<Integer> arr, int bucketSize) {
if (arr == null || arr.size() < 2)
return arr;
int max = arr.get(0), min = arr.get(0);
// 找到最大值最小值
for (int i = 0; i < arr.size(); i++) {
if (arr.get(i) > max)
max = arr.get(i);
if (arr.get(i) < min)
min = arr.get(i);
}
int bucketCount = (max - min) / bucketSize + 1;
ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketCount);
ArrayList<Integer> resultArr = new ArrayList<>();
for (int i = 0; i < bucketCount; i++) {
bucketArr.add(new ArrayList<Integer>());
}
for (int i = 0; i < arr.size(); i++) {
bucketArr.get((arr.get(i) - min) / bucketSize).add(arr.get(i));
}
for (int i = 0; i < bucketCount; i++) {
if (bucketSize == 1) {
// 如果带排序数组中有重复数字时 感谢 @见风任然是风 朋友指出错误
for (int j = 0; j < bucketArr.get(i).size(); j++)
resultArr.add(bucketArr.get(i).get(j));
} else {
if (bucketCount == 1)
bucketSize--;
ArrayList<Integer> temp = BucketSort(bucketArr.get(i), bucketSize);
for (int j = 0; j < temp.size(); j++)
resultArr.add(temp.get(j));
}
}
return resultArr;
}
桶排序最好情况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。
最佳情况 | 最差情况 | 平均情况 |
---|---|---|
T ( n ) = O ( n + k ) T(n)=O(n+k) T(n)=O(n+k) | T ( n ) = O ( n + k ) T(n)=O(n+k) T(n)=O(n+k) | T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2) |
基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
JAVA:
public static int[] RadixSort(int[] arr) {
if (arry == null || arr.length < 2)
return arr;
// 1.先算出最大数的位数;
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
max = Math.max(max, arr[i]);
}
int maxDigit = 0;
while (max != 0) {
max /= 10;
maxDigit++;
}
int mod = 10, div = 1;
ArrayList<ArrayList<Integer>> bucketList = new ArrayList<ArrayList<Integer>>();
for (int i = 0; i < 10; i++)
bucketList.add(new ArrayList<Integer>());
for (int i = 0; i < maxDigit; i++, mod *= 10, div *= 10) {
for (int j = 0; j < arr.length; j++) {
int num = (arr[j] % mod) / div;
bucketList.get(num).add(array[j]);
}
int index = 0;
for (int j = 0; j < bucketList.size(); j++) {
for (int k = 0; k < bucketList.get(j).size(); k++)
arr[index++] = bucketList.get(j).get(k);
bucketList.get(j).clear();
}
}
return arr;
}
桶排序最好情况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。
最佳情况 | 最差情况 | 平均情况 |
---|---|---|
T ( n ) = O ( n ∗ k ) T(n)=O(n*k) T(n)=O(n∗k) | T ( n ) = O ( n ∗ k ) T(n)=O(n*k) T(n)=O(n∗k) | T ( n ) = O ( n ∗ k ) T(n)=O(n*k) T(n)=O(n∗k) |
基数排序有两种方法:
这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异: