基础算法
1. 算法分类
2. 名词解释
- 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面
- 不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面
- 内排序:所有排序操作都在内存中完成
- 外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行
- 时间复杂度:一个算法执行所耗费的时间
- 空间复杂度:运行完一个程序所需内存的大小
3. 复杂度
4. 交换排序
4.1 冒泡排序
遍历若干次要排序的数列,每次遍历时,它都会从前往后依次比较相邻两个数的大小;如果前者比后者大,则交换它们的位置。这样,一次遍历之后,最大的元素就在数列的末尾! 采用相同的方法再次遍历时,第二大的元素就被排列在最大元素之前。重复此操作,直到整个数列都有序为止!
-
概要描述
- 比较相邻的元素。如果第一个比第二个大,就交换它们两个
- 对每一对相邻元素做同样的工作
- 针对所有的元素重复以上的步骤,除了最后一个
- 重复以上步骤,直到排序完成
-
静态图展示
- 算法实现(Java)
private void bubbleSort() {
int[] attr = {3, 2, 8, 4};
System.out.println("排序前: ");
Arrays.stream(attr).forEach(System.out::println);
// 实现冒泡
for (int i = 0; i < attr.length - 1; i++) {
for (int j = 0; j < attr.length - i - 1; j++) {
if (attr[j] > attr[j + 1]) {
int temp = attr[j];
attr[j] = attr[j + 1];
attr[j + 1] = temp;
}
}
}
System.out.println("排序后: ");
Arrays.stream(attr).forEach(System.out::println);
}
4.3 快速排序
在数据序列中选择一个元素作为基准值,每躺从数据序列的两端开始交替进行,将小于基准值元素交换到序列前端,将大于基准值的元素交换到序列后端,介于两者之间的位置则成为基准值的最终位置。同时,序列被划分成两个子序列,再分别对两个子序列进行快速排序,直到子序列长度为1,则完成排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
-
概要描述
- 在数据序列中选中基准值
- 从数据序列的两端交替执行,将小于基准值元素交换到序列前端,将大于基准值的元素交换到序列后端,介于两者之间的位置则成为基准值的最终位置
- 数据序列被划分为两个子序列,再对子序列进行快速排序
- 直到子序列长度为1,则完成排序,整个过程递归进行
-
静态图展示
- 算法实现(Java)
private static int[] quickSort(int[] arr, int left, int right) {
int low = left;
int high = right;
// 坐标异常,直接退出
if (low >= high) {return arr;}
// 第一个数作为基准
int pivot = arr[low];
while (low < high) {
// 右边的数字比基准数大
while (low < high && pivot <= arr[high]) {
high--;
}
// 右边元素赋值左边
arr[low] = arr[high];
// 左边元素比基准小
while (low < high && arr[low] <= pivot) {
low++;
}
arr[high] = arr[low];
}
// 把基准赋值给 low 所在位置
arr[low] = pivot;
quickSort(arr, left, low - 1);
quickSort(arr, low + 1, right);
return arr;
}
5. 插入排序
5.1 直接插入排序
把n个待排序的元素看成是一个有序表和一个无序表。开始时有序表中只包含1个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,将它插入到有序表中的适当位置,使之成为新的有序表,重复n-1次可完成排序过程。
-
概要描述
- 将待排序元素看成一个有序表与无序表
- 开始时,有序表只有一个元素,无序表有 n -1 个元素
- 每次从无序表取出第一个元素,将此插入有序表中适当的位置
- 重复2、3步 n -1 次可完成排序过程
-
静态图展示
- 算法实现(Java)
public static int[] insertSort(int[] arr) {
int j;
for (int i = 1; i < arr.length; i++) {
int index = arr[i];
// 前一个小于后后一个,则直接向后添加
for (j = i; j > 0 && index < arr[j - 1]; j--) {
arr[j] = arr[j - 1];
}
// 前一个大于后一个,需插到指定位置(已排序好的元素需集体向后移)
arr[j] = index;
}
return arr;
}
5.2 希尔排序
1959年Shell发明,第一个突破O(n^2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔(Shell)排序又称为缩小增量排序,该方法因DL.Shell于1959年提出而得名。
-
概要描述
- 记录按步长 gap 分组,对每组记录采用直接插入排序方法进行排序
- 步长逐步减小,当步长值减小到1时,整个数据合为一组,构成有序记录,则完成排序。
-
静态图展示
- 算法实现(Java)
public static int[] shellSort(int[] arr) {
// 初始时对数组进行折半,不断进行缩小增量(gap= n/2, (n/2)/2..)
for (int gap = arr.length >> 1; gap > 0; gap >>= 1) {
for (int i = gap; i < arr.length; i++) {
int index = arr[i];
int j = i;
// 小于则交换
for (; j <= gap && index < arr[j - gap]; j -= gap) {
arr[j] = arr[j - gap];
}
// 大于则不变
arr[j] = index;
}
}
return arr;
}
6. 选择排序
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
-
概要描述
- 初始状态:无序区为R[1...n],有序去为空
- 第 i 趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1…i]和R[i+1…n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区
- n-1趟结束,数组有序化
-
静态图展示
- 算法实现(Java)
public static int[] selectSort(int[] arr) {
int i, j, min, temp, len = arr.length;
for (i = 0; i < len - 1; i++) {
// 第一个元素
min = i;
for (j = i + 1; j < len; j++) {
// 挨个比较元素大小,互换坐标
if (arr[min] > arr[j]) min = j;
}
// 最小元素
temp = arr[min];
// 暂时取出元素,交换位置
arr[min] = arr[i];
// 最小值赋值
arr[i] = temp;
}
return arr;
}
7. 堆排序
堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
最小/大堆用于求最小/大值,堆序列用于多次求极值的应用问题。
-
概要描述
- 将无序序列构建成一个最大堆
- 将堆顶元素与末尾元素互换,将最大元素放到数组的末端
- 重新调整结构,使其满足最大堆的定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
-
静态图展示
- 算法实现(Java)
public static int[] sort(int[] arr, int length) {
// 初始化堆,寻找不满足最大堆定义的元素
for (int i = length / 2 - 1; i >= 0; i--) {
maxHeap(arr, length, i);
}
// 将堆顶元素与末尾元素交换,将最大元素放到数组末端
for (int i = length - 1; i >= 1; i--) {
int temp = arr[i];
arr[i] = arr[0];
arr[0] = temp;
maxHeap(arr, i, 0);
}
return arr;
}
private static void maxHeap(int[] arr, int length, int index) {
// 父节点的左子节点
int leftNode = 2 * index + 1;
// 父节点的右子节点
int rightNode = 2 * index + 2;
// 堆顶
int maxIndex = index;
// 左子节点大于父节点
if (leftNode < length && arr[maxIndex] < arr[leftNode]) {
maxIndex = leftNode;
}
// 右字节点大于父节点
if (rightNode < length && arr[maxIndex] < arr[rightNode]) {
maxIndex = rightNode;
}
// 堆顶与子节点交换位置 重复执行方法调整为大根堆
if (maxIndex != index) {
int temp = arr[maxIndex];
arr[maxIndex] = arr[index];
arr[index] = temp;
maxHeap(arr, length, maxIndex);
}
}
8. 归并排序
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
- 概要描述
- 把长度为n的输入序列分成两个长度为n/2的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
- 静态图展示
- 算法实现(Java)
/**
* 排序
*
* @param arr 数组
* @param left 起始下标
* @param right 结束下标
*/
public void sort(int[] arr, int left, int right) {
if (left >= right) return;
int mid = (left + right) >> 1;
sort(arr, left, mid);
sort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
/**
* 合并
*
* @param arr 数组
* @param left 起始下标
* @param mid 中间
* @param right 结束下标
*/
private void merge(int[] arr, int left, int mid, int right) {
int[] newArr = new int[right - left + 1];
// 左索引起始位置
int i = left;
// 右索引起始位置
int j = mid + 1;
int index = 0;
// 升序排序
while (i <= mid && j <= right) {
newArr[index++] = arr[i] < arr[j] ? arr[i++] : arr[j++];
}
// 左边元素移到临时数组底下
while (i <= mid) {
newArr[index++] = arr[i++];
}
// 右边元素移到临时数组底下
while (j <= right) {
newArr[index++] = arr[j++];
}
for (int k = 0; k < newArr.length; k++) {
arr[k + left] = newArr[k];
}
}
9. 计数排序
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。计数排序(Counting sort)是一种稳定的排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。它只能对整数进行排序。
- 概要描述
- 选出数组的最大值 k,创建一个 k+1 长度的数组 countArray,countArray 的数组下标代表待排序数组的元素值,而 countArray 中的元素值代表的是待排序数组中的每一个元素的出现次数
- 遍历待排序数组,统计每个元素出现的次数,将对应元素的出现次数写入 countArray 。例如 array[0] 是7,那么 countArray[7]++,因为countArray 的下标代表array中的数
- 对 countArray 进行循环对每一个元素 countArray[i] = countArray[i] + countArray[i-1],目的是统计每一个元素前面有多少个小于它的元素
- 复制待排序数组存到 temp 中,循环 temp,将 temp 中 i 位置的的元素放到 array 中的 --countArray[temp[i]] 位置
- 静态图展示
- 算法实现(Java)
/**
* 计数算法
*
* @param array 待排序数组
*/
public static void countSort(int[] array) {
// 获取数组最大元素
int max = getMax(array);
// 表示 0 - max 所有数字的重复次数
int[] countArray = new int[max + 1];
int[] resultArray = new int[array.length];
System.arraycopy(array, 0, resultArray, 0, array.length);
// 遍历 array 数组, countArray 的下标代表 array 中的数字,其值代表 array 中元素出现的次数
for (int j : array) {
countArray[j]++;
}
// 统计每一个元素前面有多少个小于它的元素
for (int i = 1; i < countArray.length; i++) {
countArray[i] = countArray[i] + countArray[i - 1];
}
// 将元素放入对应位置
for (int j : resultArray) {
array[--countArray[j]] = j;
}
}
/**
* 获取数组中最大的元素
*
* @param array 待排序数组
* @return 最大元素值
*/
public static int getMax(int[] array) {
int max = array[0];
for (int i = 0; i < array.length; i++) {
if (array[i] < 0) {
throw new RuntimeException("There can be no less than zero in the array");
}
if (max < array[i]) {
max = array[i];
}
}
return max;
}
10. 桶排序
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排。)
- 概要描述
- 设置一个 Bucket Size,作为每个桶所能放置多少个不同数值(例如当BucketSize==5 时,该桶可以存放{1,2,3,4,5}这几种数字,但是容量不限,即可以存放100个3)
- 遍历输入数据,并且把数据一个个放到对应的桶里
- 对每个不是空的桶进行排序,可以使用其它排序方法,也可以递归使用桶排序
- 从不是空的桶里把排好序的数据拼接起来
- 静态图展示
- 算法实现(Java)
/**
* 桶排序
*
* @param arr 待排序数组
*/
public static void bucketSort(float[] arr) {
int n = arr.length;
if (n <= 0) return;
List<Float>[] bucket = new ArrayList[n];
// 创建空桶
for (int i = 0; i < n; i++)
bucket[i] = new ArrayList<>();
// 根据规则将序列中元素分散到桶中
for (int i = 0; i < n; i++) {
int bucketIndex = (int) arr[i] * n;
bucket[bucketIndex].add(arr[i]);
}
// 对各个桶内的元素排序
for (int i = 0; i < n; i++) {
Collections.sort(bucket[i]);
}
// 合并所有桶内的元素
int index = 0;
for (int i = 0; i < n; i++) {
for (int j = 0, size = bucket[i].size(); j < size; j++) {
arr[index++] = bucket[i].get(j);
}
}
}
11. 基数排序
原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。
- MSD:先从高位开始进行排序,在每个关键字上,可采用计数排序
- LSD:先从低位开始进行排序,在每个关键字上,可采用桶排序
- 概要描述
- 将所有待比较数值统一为同样的数位长度,数位较短的数前面补零
- 从最低位开始,依次进行一次排序
- 从最低位排序一直到最高位完成以后,数列就可以变成一个有序序列
- 静态图展示
- 算法实现(Java)
/**
* 高位优先法
*
* @param arr 待排序数组
*/
private static void radixSort(int[] arr) {
// 最大值
int max = arr[0];
// 指数
int exp;
// 计算最大值
for (int anArr : arr) {
if (anArr > max) {
max = anArr;
}
}
// 从个位开始,对数组进行排序
for (exp = 1; max / exp > 0; exp *= 10) {
// 存储待排序元素的临时数组
int[] temp = new int[arr.length];
// 分桶个数
int[] buckets = new int[10];
// 将数据出现的次数存储在 buckets 中
for (int value : arr) {
// (value / exp) % 10 : value 的最底位(个位)
buckets[(value / exp) % 10]++;
}
// 更改 buckets[i]
for (int i = 1; i < 10; i++) {
buckets[i] += buckets[i - 1];
}
// 将数据存储到临时数组 temp 中
for (int i = arr.length - 1; i >= 0; i--) {
temp[buckets[(arr[i] / exp) % 10] - 1] = arr[i];
buckets[(arr[i] / exp) % 10]--;
}
// 将有序元素 temp 赋给 arr
System.arraycopy(temp, 0, arr, 0, arr.length);
}
}
引用:
https://blog.csdn.net/weixin_41948075/article/details/100499887
http://c.biancheng.net/algorithm/bucket-sort.html
https://zhuanlan.zhihu.com/p/258998553