算法对于我们今天生活十分重要,怎样宣扬也不会夸张。它们在虚拟世界中无处不在,从金融机构到交友网站。但是,相比于其他算法,其中有一些算法更大程度上改变并控制着我们的世界。本文列举了《数据结构与算法》中最基本的算法
文章目录
关于时间复杂度
平方阶 (O(n2)) 排序 各类简单排序:直接插入、直接选择和冒泡排序。
线性对数阶 (O(nlog2n)) 排序 快速排序、堆排序和归并排序;
O(n1+§)) 排序,§ 是介于 0 和 1 之间的常数。 希尔排序
线性阶 (O(n)) 排序 基数排序,此外还有桶、箱排序。
关于稳定性
稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序。
不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。
名词解释:
- n:数据规模
- k:"桶"的个数
- In-place:占用常数内存,不占用额外内存
- Out-place:占用额外内存
- 稳定性:排序后 2 个相等键值的顺序和排序之前它们的顺序相同
1 归并排序
归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:
- 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
- 自下而上的迭代;
在《数据结构与算法 JavaScript 描述》中,作者给出了自下而上的迭代方法。但是对于递归法,作者却认为:
However, it is not possible to do so in JavaScript, as the recursion goes too deep for the language to handle.
然而,在 JavaScript 中这种方式不太可行,因为这个算法的递归深度对它来讲太深了。
说实话,我不太理解这句话。意思是 JavaScript 编译器内存太小,递归太深容易造成内存溢出吗?还望有大神能够指教。
和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 O(nlogn) 的时间复杂度。代价是需要额外的内存空间。
归并排序的命名来自它的实现原理:把一系列排好序的子序列合并成一个大的完整有序序 列。从理论上讲,这个算法很容易实现。我们需要两个排好序的子数组,然后通过比较数 据大小,先从最小的数据开始插入,最后合并得到第三个数组。然而,在实际情况中,归 并排序还有一些问题,当我们用这个算法对一个很大的数据集进行排序时,我们需要相当 大的空间来合并存储两个子数组。就现在来讲,内存不那么昂贵,空间不是问题,因此值 得我们去实现一下归并排序,比较它和其他排序算法的执行效率
- 自顶向下的归并排序
通常来讲(也不一定),归并排序会使用递归的算法来实现。然而,在 JavaScript 中这种方 式不太可行,因为 这个算法的递归深度对它来讲太深了。所以,我们将使用一种非递归的方式来实现这个算法,这种策略称为自底向上的归并排序.
- 自底向上的归并排序
采用非递归或者迭代版本的归并排序是一个自底向上的过程。这个算法首先将数据集分解 为一组只有一个元素的数组。然后通过创建一组左右子数组将它们慢慢合并起来,每次合 并都保存一部分排好序的数据,直到最后剩下的这个数组所有的数据都已完美排序。
一个简单的冒泡排序的例子 如3节内容所示
1.1 算法步骤
- 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
- 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
- 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
- 重复步骤 3 直到某一指针达到序列尾;
- 将另一序列剩下的所有元素直接复制到合并序列尾。
1.2 动图演示
2.十大代码实现实现-选择排序
2.1 JavaScript 代码实现实例
function mergeSort(arr) { // 采用自上而下的递归方法
var len = arr.length;
if(len < 2) {
return arr;
}
var middle = Math.floor(len / 2),
left = arr.slice(0, middle),
right = arr.slice(middle);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right)
{
var result = [];
while (left.length && right.length) {
if (left[0] <= right[0]) {
result.push(left.shift());
} else {
result.push(right.shift());
}
}
while (left.length)
result.push(left.shift());
while (right.length)
result.push(right.shift());
return result;
}
2.2 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
2.3 Go 代码实现实例
func mergeSort(arr []int) []int {
length := len(arr)
if length < 2 {
return arr
}
middle := length / 2
left := arr[0:middle]
right := arr[middle:]
return merge(mergeSort(left), mergeSort(right))
}
func merge(left []int, right []int) []int {
var result []int
for len(left) != 0 && len(right) != 0 {
if left[0] <= right[0] {
result = append(result, left[0])
left = left[1:]
} else {
result = append(result, right[0])
right = right[1:]
}
}
for len(left) != 0 {
result = append(result, left[0])
left = left[1:]
}
for len(right) != 0 {
result = append(result, right[0])
right = right[1:]
}
return result
}
2.4 Java 代码实现实例
public class MergeSort implements IArraySort {
@Override
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
if (arr.length < 2) {
return arr;
}
int middle = (int) Math.floor(arr.length / 2);
int[] left = Arrays.copyOfRange(arr, 0, middle);
int[] right = Arrays.copyOfRange(arr, middle, arr.length);
return merge(sort(left), sort(right));
}
protected int[] merge(int[] left, int[] right) {
int[] result = new int[left.length + right.length];
int i = 0;
while (left.length > 0 && right.length > 0) {
if (left[0] <= right[0]) {
result[i++] = left[0];
left = Arrays.copyOfRange(left, 1, left.length);
} else {
result[i++] = right[0];
right = Arrays.copyOfRange(right, 1, right.length);
}
}
while (left.length > 0) {
result[i++] = left[0];
left = Arrays.copyOfRange(left, 1, left.length);
}
while (right.length > 0) {
result[i++] = right[0];
right = Arrays.copyOfRange(right, 1, right.length);
}
return result;
}
}
2.5 PHP 代码实现实例
function mergeSort($arr)
{
$len = count($arr);
if ($len < 2) {
return $arr;
}
$middle = floor($len / 2);
$left = array_slice($arr, 0, $middle);
$right = array_slice($arr, $middle);
return merge(mergeSort($left), mergeSort($right));
}
function merge($left, $right)
{
$result = [];
while (count($left) > 0 && count($right) > 0) {
if ($left[0] <= $right[0]) {
$result[] = array_shift($left);
} else {
$result[] = array_shift($right);
}
}
while (count($left))
$result[] = array_shift($left);
while (count($right))
$result[] = array_shift($right);
return $result;
}
2.6 C 代码实现实例
int min(int x, int y) {
return x < y ? x : y;
}
void merge_sort(int arr[], int len) {
int *a = arr;
int *b = (int *) malloc(len * sizeof(int));
int seg, start;
for (seg = 1; seg < len; seg += seg) {
for (start = 0; start < len; start += seg * 2) {
int low = start, mid = min(start + seg, len), high = min(start + seg * 2, len);
int k = low;
int start1 = low, end1 = mid;
int start2 = mid, end2 = high;
while (start1 < end1 && start2 < end2)
b[k++] = a[start1] < a[start2] ? a[start1++] : a[start2++];
while (start1 < end1)
b[k++] = a[start1++];
while (start2 < end2)
b[k++] = a[start2++];
}
int *temp = a;
a = b;
b = temp;
}
if (a != arr) {
int i;
for (i = 0; i < len; i++)
b[i] = a[i];
b = a;
}
free(b);
}
2.7 C++ 代码实现实例
template<typename T> // 整数或浮点数皆可使用,若要使用物件(class)时必须设定"小于"(<)的运算子功能
void merge_sort(T arr[], int len) {
T *a = arr;
T *b = new T[len];
for (int seg = 1; seg < len; seg += seg) {
for (int start = 0; start < len; start += seg + seg) {
int low = start, mid = min(start + seg, len), high = min(start + seg + seg, len);
int k = low;
int start1 = low, end1 = mid;
int start2 = mid, end2 = high;
while (start1 < end1 && start2 < end2)
b[k++] = a[start1] < a[start2] ? a[start1++] : a[start2++];
while (start1 < end1)
b[k++] = a[start1++];
while (start2 < end2)
b[k++] = a[start2++];
}
T *temp = a;
a = b;
b = temp;
}
if (a != arr) {
for (int i = 0; i < len; i++)
b[i] = a[i];
b = a;
}
delete[] b;
}
2.8 C# 代码实现实例
public static List<int> sort(List<int> lst) {
if (lst.Count <= 1)
return lst;
int mid = lst.Count / 2;
List<int> left = new List<int>(); // 定义左侧List
List<int> right = new List<int>(); // 定义右侧List
// 以下两个循环把 lst 分为左右两个 List
for (int i = 0; i < mid; i++)
left.Add(lst[i]);
for (int j = mid; j < lst.Count; j++)
right.Add(lst[j]);
left = sort(left);
right = sort(right);
return merge(left, right);
}
/// <summary>
/// 合併两个已经排好序的List
/// </summary>
/// <param name="left">左侧List</param>
/// <param name="right">右侧List</param>
/// <returns></returns>
static List<int> merge(List<int> left, List<int> right) {
List<int> temp = new List<int>();
while (left.Count > 0 && right.Count > 0) {
if (left[0] <= right[0]) {
temp.Add(left[0]);
left.RemoveAt(0);
} else {
temp.Add(right[0]);
right.RemoveAt(0);
}
}
if (left.Count > 0) {
for (int i = 0; i < left.Count; i++)
temp.Add(left[i]);
}
if (right.Count > 0) {
for (int i = 0; i < right.Count; i++)
temp.Add(right[i]);
}
return temp;
}
2.9 Ruby 代码实现实例
def merge list
return list if list.size < 2
pivot = list.size / 2
# Merge
lambda { |left, right|
final = []
until left.empty? or right.empty?
final << if left.first < right.first; left.shift else right.shift end
end
final + left + right
}.call merge(list[0...pivot]), merge(list[pivot..-1])
end
2.10 Swift 代码实现实例
无
3.一个简单的归并排序的例子
下图演示了 自底向上 的归并排序算法是如何运行的
在展示归并排序的 JavaScript 代码之前,我们先来看一个 JavaScript 程序的输出结果,它采
用自底向上的归并排序算法对一个包含 10 个整数的数组进行排序
6,10,1,9,4,8,2,7,3,5
left array - 6,Infinity
right array - 10,Infinity
left array - 1,Infinity
right array - 9,Infinity
left array - 4,Infinity
right array - 8,Infinity
left array - 2,Infinity
right array - 7,Infinity
left array - 3,Infinity
right array - 5,Infinity
left array - 6,10,Infinity
right array - 1,9,Infinity
left array - 4,8,Infinity
right array - 2,7,Infinity
left array - 1,6,9,10,Infinity
right array - 2,4,7,8,Infinity
left array - 1,2,4,6,7,8,9,10,Infinity
right array - 3,5,Infinity
1,2,3,4,5,6,7,8,9,10
Infinity 这个值用于标记左子序列或右子序列的结尾。
一开始每个元素都在左子序列或右子序列中。然后将左右子序列合并,首先每次合并成两
个元素的子序列,然后合并成四个元素的子序列,3 和 5 除外,它们会一直保留到最后一次
迭代,那时会把它们合并成右子序列,然后再与最后的左子序列合并成最终的有序数组。
现在我们知道了自底向上的归并排序的工作原理,例 1 就是输出上述结果的代码。
例 1 JavaScript 实现的自底向上归并排序算法
function mergeSort(arr) {
if (arr.length < 2) {
return;
}
var step = 1;
var left,
right;
while (step < arr.length) {
left = 0;
right = step;
while (right + step <= arr.length) {
mergeArrays(arr, left, left + step, right, right + step);
left = right + step;
right = left + step;
}
if (right < arr.length) {
mergeArrays(arr, left, left + step, right, arr.length);
}
step *= 2;
}
}
function mergeArrays(arr, startLeft, stopLeft, startRight, stopRight) {
var rightArr = new Array(stopRight - startRight + 1);
var leftArr = new Array(stopLeft - startLeft + 1);
k = startRight;
for (var i = 0; i < (rightArr.length - 1); ++i) {
rightArr[i] = arr[k];
++k;
}
k = startLeft;
for (var i = 0; i < (leftArr.length - 1); ++i) {
leftArr[i] = arr[k];
++k;
}
rightArr[rightArr.length - 1] = Infinity; // 哨兵值
leftArr[leftArr.length - 1] = Infinity; // 哨兵值
var m = 0;
var n = 0;
for (var k = startLeft; k < stopRight; ++k) {
if (leftArr[m] <= rightArr[n]) {
arr[k] = leftArr[m];
m++;
} else {
arr[k] = rightArr[n];
n++;
}
}
print("left array - ", leftArr);
print("right array - ", rightArr);
}
var nums = [6, 10, 1, 9, 4, 8, 2, 7, 3, 5];
print(nums);
print();
mergeSort(nums);
print();
print(nums)
//mergeSort() 函数中的关键点就是 step 这个变量,它用来控制 mergeArrays() 函数生成的
//leftArr 和 rightArr 这两个子序列的大小。通过控制子序列的大小,处理排序是比较高效
//的,因为它在对小数组进行排序时不需要花费太多时间。合并之所以高效,还有一个原
//因,由于未合并的数据已经是排好序的,将它们合并到一个有序数组的过程非常容易
下一步将归并排序添加到 CArray 类中,并记录它处理大数据集的时间。例 12-14 展示了已 添加 mergeSort() 和 mergeArrays() 函数的 CArray 类的定义
已添加归并排序的 CArray 类
function CArray(numElements) {
this.dataStore = [];
this.pos = 0;
this.gaps = [5, 3, 1];
this.numElements = numElements;
this.insert = insert;
this.toString = toString;
this.clear = clear;
this.setData = setData;
this.setGaps = setGaps;
this.shellsort = shellsort;
this.mergeSort = mergeSort;
this.mergeArrays = mergeArrays;
for (var i = 0; i < numElements; ++i) {
this.dataStore[i] = 0;
}
}
// 其他函数的定义在这里
function mergeArrays(arr, startLeft, stopLeft, startRight, stopRight) {
var rightArr = new Array(stopRight - startRight + 1);
var leftArr = new Array(stopLeft - startLeft + 1);
k = startRight;
for (var i = 0; i < (rightArr.length - 1); ++i) {
rightArr[i] = arr[k];
++k;
}
k = startLeft;
for (var i = 0; i < (leftArr.length - 1); ++i) {
leftArr[i] = arr[k];
++k;
}
rightArr[rightArr.length - 1] = Infinity; // 哨兵值
leftArr[leftArr.length - 1] = Infinity; // 哨兵值
var m = 0;
var n = 0;
for (var k = startLeft; k < stopRight; ++k) {
if (leftArr[m] <= rightArr[n]) {
arr[k] = leftArr[m];
m++;
} else {
arr[k] = rightArr[n];
n++;
}
}
print("left array - ", leftArr);
print("right array - ", rightArr);
}
function mergeSort() {
if (this.dataStore.length < 2) {
return;
}
var step = 1;
var left,
right;
while (step < this.dataStore.length) {
left = 0;
right = step;
while (right + step <= this.dataStore.length) {
mergeArrays(this.dataStore, left, left + step, right, right + step);
left = right + step;
right = left + step;
}
if (right < this.dataStore.length) {
mergeArrays(this.dataStore, left, left + step, right, this.dataStore.length);
}
step *= 2;
}
}
var nums = new CArray(10);
nums.setData();
print(nums.toString());
nums.mergeSort();
print(nums.toString());