十大经典排序算法C++和Python实现总结(动图演示)

一、算法概述

1. 算法分类

十种经典的排序算法可以分为两大类:

  1. 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
  2. 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。
    十大经典排序算法C++和Python实现总结(动图演示)_第1张图片

2. 算法复杂度

十大经典排序算法C++和Python实现总结(动图演示)_第2张图片

3. 相关概念

  1. 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
  2. 不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
    注:冒泡排序、插入排序、归并排序和基数排序为稳定的排序算法;选择排序、快速排序、希尔排序、堆排序为不稳定的排序算法。
  3. 时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
  4. 空间复杂度:是指算法在计算机

二、冒泡排序

冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

1. 算法步骤

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

2. 动图演示

3. 代码实现

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 ; 
}

4. 性能分析

当输入的数据已经是正序时,算法最快;当输入的数据是反序时,算法最慢。

最佳情况 最差情况 平均情况
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²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。

1. 算法步骤

  1. 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
  2. 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
  3. 重复第二步,直到所有元素均排序完毕。

2. 动图演示

3. 代码实现

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 ;
}

4. 性能分析

最佳情况 最差情况 平均情况
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)

四、插入排序

插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。

1. 算法步骤

  1. 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
  2. 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)

2. 动图演示

3. 代码实现

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 ;
}

4. 性能分析

最佳情况 最差情况 平均情况
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)

五、希尔排序

希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  • 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
  • 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;

希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。

1. 算法步骤

  1. 选择一个增量序列 t 1 t_1 t1 t 2 t_2 t2 … … …… t k t_k tk,其中 t i > t j t_i> t_j ti>tj, t k = 1 t_k = 1 tk=1
  2. 按增量序列个数 k,对序列进行 k 趟排序;
  3. 每趟排序,根据对应的增量 t i t_i ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

2. 动图演示

3. 代码实现

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 ;
}

4. 性能分析

最佳情况 最差情况 平均情况
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)的一个非常典型的应用。

作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:

  • 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
  • 自下而上的迭代;

和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 O(nlogn) 的时间复杂度。代价是需要额外的内存空间。

1. 算法步骤

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
  4. 重复步骤 3 直到某一指针达到序列尾;
  5. 将另一序列剩下的所有元素直接复制到合并序列尾。

2. 动图演示

3. 代码实现

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;
}

4. 性能分析

最佳情况 最差情况 平均情况
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) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。

1. 算法步骤

  1. 从数列中挑出一个元素,称为 “基准”(pivot);
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

2. 动图演示

3. 代码实现

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;
}

4. 性能分析

最佳情况 最差情况 平均情况
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)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:

  1. 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
  2. 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;

堆排序的平均时间复杂度为 Ο(nlogn)。

1. 算法步骤

  1. 将待排序序列构建成一个堆 H[0……n-1],根据(升序降序需求)选择大顶堆或小顶堆;
  2. 把堆首(最大值)和堆尾互换;
  3. 把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
  4. 重复步骤 2,直到堆的尺寸为 1。

2. 动图演示

3. 代码实现

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);
	}
}

九、计数排序

计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

1. 算法步骤

  1. 找出待排序的数组中最大和最小的元素;
  2. 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
  3. 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
  4. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。

2. 动图演示

3. 代码实现

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;
}

4. 性能分析

当输入的元素是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. 在额外空间充足的情况下,尽量增大桶的数量
  2. 使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中

同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。

1. 算法步骤

  1. 人为设置一个BucketSize,作为每个桶所能放置多少个不同数值(例如当BucketSize==5时,该桶可以存放{1,2,3,4,5}这几种数字,但是容量不限,即可以存放100个3);
  2. 遍历输入数据,并且把数据一个一个放到对应的桶里去;
  3. 对每个不是空的桶进行排序,可以使用其它排序方法,也可以递归使用桶排序;
  4. 从不是空的桶里把排好序的数据拼接起来。

注意,如果递归使用桶排序为各个桶排序,则当桶数量为1时要手动减小BucketSize增加下一循环桶的数量,否则会陷入死循环,导致内存溢出。

2. 图片演示

十大经典排序算法C++和Python实现总结(动图演示)_第3张图片

3. 代码实现

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;
}

4. 性能分析

桶排序最好情况下使用线性时间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)

十一、基数排序

基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。

1. 算法描述

  1. 取得数组中的最大数,并取得位数;
  2. arr为原始数组,从最低位开始取每个位组成radix数组;
  3. 对radix进行计数排序(利用计数排序适用于小范围数的特点);

2. 动图演示

3. 代码实现

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;
}

4. 性能分析

桶排序最好情况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。

最佳情况 最差情况 平均情况
T ( n ) = O ( n ∗ k ) T(n)=O(n*k) T(n)=O(nk) T ( n ) = O ( n ∗ k ) T(n)=O(n*k) T(n)=O(nk) T ( n ) = O ( n ∗ k ) T(n)=O(n*k) T(n)=O(nk)
5. 基数排序 vs 计数排序 vs 桶排序

基数排序有两种方法:

这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:

  • 基数排序:根据键值的每位数字来分配桶;
  • 计数排序:每个桶只存储单一键值;
  • 桶排序:每个桶存储一定范围的数值

你可能感兴趣的:(C++,算法)