冒泡排序
最简单的排序算法
时间复杂度
O(N²)
思路
对于每个元素i,若等于其后相邻的元素,则交换位置
代码
function bubbleSort(arr){
for(let i = 0; i < arr.length - 1; i++){
for(let j = 0; j < arr.length - 1 - i; j++){
if(arr[j] > arr[j+1]){
swap(arr,j,j+1);
}
}
}
console.log(arr);
}
function swap(arr,i,j){
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
bubbleSort([1,3,5,4,3,2,1,5,7,4,3,1])
复制代码
public class BubbleSort{
public static void main(String[] rgs){
int[] nums = {1,3,5,7,6,5,4,3,2,1,7};
bubbleSort(nums);
for(int n : nums)
System.out.println(n);
}
public static void bubbleSort(int[] arr){
for(int i = 0; i < arr.length-1; i++){
for(int j = 0; j < arr.length - 1 - i; j++){
if(arr[j] > arr[j+1]){
int temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
}
}
复制代码
选择排序
时间复杂度
O(n²)
思路
在每轮循环中,寻找未排序区间内最小的元素,与未排序区间的首元素交换,逐渐将整个区间整理为已排序区间
代码
function selectSort(arr){
for(let i = 0; i < arr.length - 1; i++){
let min = i;
for(let j = i + 1; j < arr.length; j++){
if(arr[j] < arr[min]){
min = j;
}
}
swap(arr,i,min);
}
}
function swap(arr,i,j){
const temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
复制代码
public class SelectSort{
public static void main(String[] args){
int[] nums = {1,3,5,6,5,4,3,2,1};
selectSort(nums);
for(int n : nums)
System.out.println(n);
}
public static void selectSort(int[] nums){
for(int i = 0; i < nums.length - 1; i++){
int min = i;
for(int j = i + 1; j < nums.length; j++){
if(nums[j] < nums[min]){
min = j;
}
}
swap(nums,i,min);
}
}
public static void swap(int[] nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
复制代码
插入排序
时间复杂度
O(N²)
思路
将整个区间划分为两块,已排序和未排序。
从第1个元素开始,向前查找,若nums[i] < nums[i-1],则交换位置
代码
function insertSort(nums){
for(let i = 1; i < nums.length; i++){
const temp = nums[i];
let j = i;
while(j > 0 && nums[j-1] > temp){
nums[j] = nums[j-1];
j--;
}
nums[j] = temp
}
console.log(nums);
}
复制代码
public class InsertSort{
public static void main(String[] args){
int[] nums = {1,3,5,7,2,4,6,8,7,6,5,4,3,2,1};
insertSort(nums);
for(int n : nums)
System.out.println(n);
}
public static void insertSort(int[] nums){
for(int i = 1; i < nums.length; i++){
int temp = nums[i];
int j = i;
while(j > 0 && nums[j-1] > temp){
nums[j] = nums[j-1];
j--;
}
nums[j] = temp;
}
}
}
复制代码
希尔排序
插入排序的优化版本
时间复杂度
- 最坏时间复杂度:O(N²),使用特殊希尔增量时为O(N*log²N),小于O(N²)
- 最优时间复杂度:O(N)
- 平均时间复杂度:取决于希尔增量的选取
思路
希尔排序是对插入排序的优化。
在插入排序中,每次元素移动只能和相邻的元素交换,因此当一个较小元素出现在数组后方时,需要多次移动才能移到前端。
在希尔排序中,选取一个步长step,坐标相邻为step的元素组成一组。每个组内进行插入排序,这样可以实现元素的快速移动,避免插入排序中出现的极端情况。
步长的选择是希尔排序的重要部分,选择合理的步长可以降低时间复杂度,已知的最好步长序列是Sedgewick提出的(1, 5, 19, 41, 109,…)
实际编码中,可以用length/2作为起始步长,并逐渐减半
代码
function shellSort(nums){
// 选取步长,每次减半
for(let step = nums.length>>1; step >= 1; step = step>>1){
// 向前插入排序
for(let i = step; i < nums.length; i++){
let temp = nums[i];
let j = i;
while(nums[j-step] > temp){
nums[j] = nums[j-step];
j -= step;
}
nums[j] = temp;
}
}
console.log(nums);
}
复制代码
public class ShellSort{
public static void main(String[] args){
int[] nums = {1,3,5,7,6,5,4,3,2,1,7};
sort(nums);
for(int n : nums)
System.out.println(n);
}
public static void sort(int[] nums){
for(int step = nums.length / 2; step > 0; step /= 2){
for(int i = step; i < nums.length; i++){
int temp = nums[i];
int j = i;
while(j > 0 && nums[j-1] > temp){
nums[j] = nums[j-1];
j--;
}
nums[j] = temp;
}
}
}
}
复制代码
快速排序
时间复杂度
O(N*logN)
思路
- 每轮将第一个元素作为标准v,将数组的区间分为两部分:大于v的和小于v的
- 找到v最终的坐标后,对前后两个部分递归调用quickSort进行排序
代码
JS版
function sort(arr){
quickSort(arr,0,arr.length-1);
return arr;
}
function quickSort(arr,start,end){
if(start < 0 || end >= arr.length || start >= end) return;
const index = partition(arr,start,end);
quickSort(arr,start,index-1);
quickSort(arr,index+1,end);
}
function partition(arr,start,end){
let left = start, right = end, p = start + 1;
while(left < right){
if(arr[p] < arr[start]){
left++;
p++;
}else{
swap(arr,p,right);
right--;
}
}
swap(arr,start,left);
return left;
}
function swap(arr,i,j){
const temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
console.log(sort([1,3,5,7,8,6,4,2,1,4,5,6,3]))
复制代码
Java版
public class QuickSort{
public static void main(String[] args){
int[] arr = {1,3,5,7,9,8,6,4,2,4,7};
quickSort(arr,0,arr.length-1);
for(int n : arr)
System.out.println(n);
}
public static void quickSort(int[] arr,int start,int end){
if(start < 0 || end >= arr.length || start >= end) return;
int index = partition(arr,start,end);
quickSort(arr, start, index-1);
quickSort(arr, index+1, end);
}
public static int partition(int[] arr, int start, int end){
int left = start,
right = end,
p = start + 1;
while(left < right){
if(arr[p] < arr[start]){
p++;
left++;
}else{
swap(arr,p,right);
right--;
}
}
swap(arr,start,left);
return left;
}
public static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
复制代码
归并排序
分治法的经典应用
时间复杂度
O(N*logN)
思路
现将数组分成两个部分,将两个子数组分别排序后归并到一起,即为答案
代码
function sort(arr){
mergeSort(arr,0,arr.length-1);
console.log(arr);
}
function mergeSort(arr,start,end){
if(end === start) return;
let mid = Math.floor((end + start) / 2);
mergeSort(arr,start,mid);
mergeSort(arr,mid+1,end);
merge(arr,start,mid,end);
}
function merge(arr,start,mid,end){
let l = start, r = mid+1, temp = [];
while(l <= mid && r <= end){
if(arr[l] < arr[r]){
temp.push(arr[l++]);
}else{
temp.push(arr[r++]);
}
}
while(l <= mid){
temp.push(arr[l++]);
}
while(r <= end){
temp.push(arr[r++]);
}
for(let i = start; i <= end; i++){
arr[i] = temp[i-start];
}
}
sort([1,3,5,7,2,4,6,8,7,6,5,4,3,2,1])
复制代码
public class MergeSort{
public static void main(String[] args){
int[] nums = {1,3,5,7,2,4,6,8,7,6,5,4,3,2,1};
mergeSort(nums,0,nums.length-1);
for(int n : nums)
System.out.println(n);
}
public static void mergeSort(int[] nums, int start, int end){
if(start == end) return;
int mid = (start + end) / 2;
mergeSort(nums, start, mid);
mergeSort(nums, mid+1, end);
merge(nums, start, mid, end);
}
public static void merge(int[] nums, int start, int mid, int end){
int l = start, r = mid + 1, cur = 0;
int[] temp = new int[end-start+1];
while(l <= mid && r <= end){
if(nums[l] < nums[r]){
temp[cur++] = nums[l++];
}else{
temp[cur++] = nums[r++];
}
}
while(l <= mid){
temp[cur++] = nums[l++];
}
while(r <= end){
temp[cur++] = nums[r++];
}
for(int i = start; i <= end; i++){
nums[i] = temp[i-start];
}
}
}
复制代码
堆排序
时间复杂度
O(NlogN)
思路
- 将给定数组整理为一个大顶堆
大顶堆:一种特殊的二叉树。对于每个节点,其子节点的值一定小于自身
- 将堆顶元素和堆中最后一个元素交换位置,将其视为移出大顶堆
- 将剩余元素重新整理为大顶堆
- 重复上述过程,直到堆中只有一个元素
代码
function heapSort(nums){
// 构造大顶堆
buildMaxHeap(nums);
// 将大顶堆的堆顶元素与堆中最后一个元素交换位置,并移出大顶堆
for(let i = nums.length-1; i > 0; i--){
swap(nums,0,i);
// 将剩余部分重新整理为大顶堆
heapify(nums,0,i-1);
}
console.log(nums);
}
/**
* 将数组构造成大顶堆
*/
function buildMaxHeap(nums){
const len = nums.length;
let leaf = len>>1;
for(;leaf>=0; leaf--){
heapify(nums,leaf,len-1);
}
}
/**
* 将nums数组的[0,len]区间内,以i为根节点的子树整理为大顶堆
*/
function heapify(nums,i,len){
// 子节点指针,先指向左孩子
let child = i*2+1;
// 若超出范围,则返回
if(child > len) return;
// 若右孩子存在,将child指向子节点中较大的一个
if(child < len && nums[child+1] > nums[child]){
child++;
}
// 若子节点比根节点大,则交换两节点,递归调用heapify函数
if(nums[child] > nums[i]){
swap(nums,i,child);
heapify(nums,child,len);
}
}
function swap(nums,i,j){
const temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
heapSort([1,3,5,7,2,4,6,8,7,6,5,4,3,2,1])
复制代码
public class HeapSort{
public static void main(String[] args){
int[] nums = {1,3,5,7,2,4,6,8,7,6,5,4,3,2,1};
HeapSort(nums);
for(int n : nums)
System.out.println(n);
}
public static void swap(int[] nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
public static void HeapSort(int[] nums){
buildMaxHeap(nums);
for(int i = nums.length-1; i > 0; i--){
swap(nums,0,i);
heapify(nums,0,i-1);
}
}
public static void buildMaxHeap(int[] nums){
int len = nums.length;
int leaf = len>>1;
for(; leaf >= 0; leaf--){
heapify(nums,leaf,len-1);
}
}
public static void heapify(int[] nums, int root, int len){
int child = root * 2 + 1;
if(child > len) return;
if(child < len && nums[child+1] > nums[child]){
child++;
}
if(nums[child] > nums[root]){
swap(nums,child,root);
heapify(nums,child,len);
}
}
}
复制代码
计数排序
时间复杂度
当输入的元素是 n 个 0 到 k 之间的整数时,它的运行时间是 O(n + k)
思路
计数排序是非比较算法。
- 首先确定数组的最大值。构造一个额外空间bucket,用于存放各个数据出现的次数
- 遍历数组,统计各个数据出现的次数
- 遍历bucket,将数字写入待排序数组nums
代码
function countSort(nums,max){
const bucket = Array(max+1).fill(0);
let index = 0;
for(let item of nums){
bucket[item]++;
}
for(let i = 0; i < bucket.length;i++){
while(bucket[i] > 0){
nums[index++] = i;
bucket[i]--;
}
}
console.log(nums);
}
复制代码
import java.util.Arrays;
public class CountSort{
public static void main(String[] args){
int[] nums = {1,3,5,7,2,4,6,8,7,6,5,4,3,2,1};
countSort(nums,8);
for(int n : nums)
System.out.println(n);
}
public static void countSort(int[] nums, int max){
int[] bucket = new int[max+1];
int index = 0;
Arrays.fill(bucket,0);
for(int i:nums){
bucket[i]++;
}
for(int i = 0; i <= max; i++){
while(bucket[i] > 0){
nums[index++] = i;
bucket[i]--;
}
}
}
}
复制代码
桶排序
时间复杂度
当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间,即O(n)
思路
- 现将数组分配到有限个桶内(尽量均匀分配)
- 对每个桶内的元素进行排序(递归调用桶排序或是用其他排序方法)
- 将桶内元素整合到待排序数组中
代码
function bucketSort(nums){
if(nums.length <= 1) return;
// 数组中的最大值和最小值
const min = Math.min(...nums);
const max = Math.max(...nums);
// 桶的体积,这里设置为5
const bucketSize = 5;
// 桶的数量
const bucketNum= Math.floor((max - min) / bucketSize) + 1;
// 用来存放每组数据的桶
const bucket = Array(bucketNum).fill(0).map(() => []);
for(let n of nums){
const idnex = Math.floor((n-min) / bucketSize);
bucket[idnex].push(n);
}
let cur = 0;
for(let i = 0; i < bucketNum; i++){
if(bucket[i].length > 0){
// 对桶内的元素进行排序,这里使用的是插入排序,可以选择用任何排序算法
insertSort(bucket[i]);
for(let j = 0; j < bucket[i].length; j++){
nums[cur++] = bucket[i][j]
}
}
}
console.log(nums);
}
复制代码
import java.util.Arrays;
public class BucketSort{
public static void main(String[] args){
int[] nums = {1,3,5,7,2,4,6,8,7,6,5,4,3,2,1};
bucketSort(nums);
for(int n : nums)
System.out.println(n);
}
public static void bucketSort(int[] nums){
int max = nums[0], min = nums[0];
for(int i:nums){
if(i > max) max = i;
else if(i < min) min = i;
}
int bucketNum = 5, bucketSize = nums.length;
int[][] bucket = new int[bucketNum][bucketSize];
int[] bIndex = {0,0,0,0,0};
for(int i=0;i<bucketNum;i++){
Arrays.fill(bucket[i],-1);
}
for(int i: nums){
int index = (i-min) / bucketSize - (i-min) % bucketSize == 0 ? 0 : 1;
bucket[index][bIndex[index]++] = i;
}
int cur = 0;
for(int i = 0; i < bucketNum; i++){
Arrays.sort(bucket[i]);
for(int n:bucket[i]){
if(n != -1){
nums[cur++] = n;
}
}
}
}
}
复制代码
基数排序
只适用于整数、有限位数的小数、格式有规律的字符串(如日期)
思路
对于一些长度为n位的整数,共需从低位到高位进行n轮排序。
使用10个桶(bucket),分别代表该位数字是0-9的数据。
每轮循环中,进行分配和回收
- 分配:将数据根据某一位的数字,分配到对应的bucket中
- 回收:从每个bucket中依次回收所有数据,写入原数组中
在完成最后一趟回收后,原数组的更新完成。
代码
function radixSort(nums){
// 求数字的长度
const len = String(Math.max(...nums)).length;
const bucket = Array(10).fill(0).map(() => []);
for(let i = 0; i < len; i++){
// 从最后一位开始,对每一位进行排序
assign(bucket,nums,i);
// 一轮排序完成后,进行回收
recycle(bucket,nums);
}
console.log(nums)
}
function assign(bucket,nums,i){
for(let n of nums){
const bit = getBit(n,i);
bucket[bit].push(n);
}
}
function recycle(bucket,nums){
let cur = 0;
for(let i = 0; i < bucket.length; i++){
for(let n of bucket[i]){
nums[cur++] = n;
}
bucket[i] = [];
}
}
/**
* 获取数字n的从右数第i位,从0开始计
*/
function getBit(n,i){
const str = String(n);
const index = str.length - 1 - i;
return index >= 0 ? Number(str[index]) : 0;
}
radixSort([99,189,204,357,100,121,156,80])
复制代码
import java.util.Arrays;
public class RadixSort{
public static void main(String[] args){
int[] nums = {999,189,204,357,100,121,156,800};
radixSort(nums);
for(int n : nums)
System.out.println(n);
}
public static void radixSort(int[] nums){
int len = (nums[0] + "").length();
int[][] bucket = new int[10][nums.length];
for(int i = 0; i < 10; i++){
Arrays.fill(bucket[i],-1);
}
for(int i = 0; i < len; i++){
int[] cur = {0,0,0,0,0,0,0,0,0,0};
assign(bucket, cur, nums, i);
recycle(bucket, nums);
}
}
public static void assign(int[][] bucket, int[] cur, int[] nums, int i){
for(int n: nums){
int bit = getBit(n,i);
bucket[bit][cur[bit]++] = n;
}
}
public static void recycle(int[][] bucket, int[] nums){
int cur = 0;
for(int i = 0; i < 10; i++){
for(int n: bucket[i]){
if(n != -1){
nums[cur++] = n;
}
}
bucket[i] = new int[nums.length];
Arrays.fill(bucket[i],-1);
}
}
public static int getBit(int n, int i){
int len = (n + "").length();
if(len <= i) return 0;
int x = n % (int)Math.pow(10,i);
int y = n % (int)Math.pow(10,i+1);
return (y - x) / (int)Math.pow(10,i);
}
}
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END