第一天 选择排序
选择排序是一种简单直观的排序算法,无论什么数据进去都是 O ( n 2 ) \Omicron(n²) O(n2) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。
1、算法步骤
- 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
- 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
- 重复第二步,直到所有元素均排序完毕。
和冒泡排序相比,选择排序比冒泡排序效率高,高在交换位置的次数上
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 void selectSort(int[] arr){ //选择排序
for(int i = 0; i < arr.length; i++){
int min = i;
for(int j = i+1; j < arr.length; ++j){
if(arr[min] > arr[j]){
min = j;
}
}
if(min != i){
int tmp = arr[i];
arr[i] = arr[min];
arr[min] = tmp;
}
}
}
4、应用
python方法的应用:
力扣75.颜色分类
力扣4.寻找两个正序数组的中位数
力扣747.至少是其他数字两倍的最大数
力扣1502. 判断能否形成等差数列
Java方法的应用:
力扣611.有效三角形的个数
第二天 冒泡排序
冒泡排序(Bubble Sort)也是一种简单直观的排序算法。又称为泡式排序。
核心思想:它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列)。
1、算法步骤(以升序为例)
- 比较相邻的元素。如果第一个比第二个大,就交换它们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
2、动图演示
3、代码
c
void bubbleSort(int *a, int n){
int i, j;
for(i = n-1; i > 0; --i){ //首先枚举序列的右端点,从 n-1 到 0,逆序枚举
for(j = 0; j < i; ++j){ //第二层循环从 0 开始
if(a[j] > a[j+1]){ //如果发现前一个数大于后一个数,对这两个数进行交换
int tmp = a[j];
a[j] = a[j+1];
a[j+1] = tmp; //这样保证 a[j+1] 一定比 a[j] 大。
}
}
}
}
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 void BubbleSort(int[] nums) {
for(int i = nums.length-1; i >= 0; i--){ //(1)
for(int j = 0; j < i; j++){ //(2)
if(nums[j] > nums[j+1]){
int tmp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = tmp;
flag = false;
}
}
}
}
(1):因为每次比较两个数,所以总共n个数,只需要比较n-1次 (外循环n-1次) ;
(2): 每次比较完后,最大的值在下一次比较中不用比较,所以每次比较只需要循环n-1-i次 (内循环n-1-i次)。
class Solution {
public void sortColors(int[] nums) {
for(int i = nums.length-1; i >= 0; i--){
boolean flag = true; //(1)
for(int j = 0; j < i; j++){
if(nums[j] > nums[j+1]){
int tmp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = tmp;
flag = false; //(2)
}
}
if(flag){
break;
}
}
}
}
(1) :标记本轮循环下来,是否有交换过元素
(2) : 更新标记
- false: 表示当前循环交换过元素
- true : 表示当前循环没有交换过元素,那说明后面的元素其实已经都有序
4、应用
1046. 最后一块石头的重量
2148. 元素计数
88. 合并两个有序数组
1464. 数组中两元素的最大乘积
Java版本
75. 颜色分类
第三天 插入排序
插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。
插入排序是一种最简单直观的排序算法。
它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到正确位置并插入。
插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。
1、算法步骤
- 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
- 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
2、动图演示
3、代码
c
void insertionSort(int *a, int n){ //插入排序
int i,j;
for(i = 1; i < n; ++i){
int tmp = a[i];
for(j = i-1; j >= 0; --j){
if(tmp < a[j]){
a[j+1] = a[j];
}else {
break;
}
}
a[j+1] = tmp;
}
}
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
具体实现: 使用双层循环,外层循环枚举除了第一个元素之外的所有元素,内层循环遍历当前元素前面的有序表,进行待插入位置查找,并进行移动。
4、应用
c实现版本
1619. 删除某些元素后的数组均值
1491. 去掉最低工资和最高工资后的工资平均值
1984. 学生分数的最小差值
Java实现版本
88. 合并两个有序数组
147. 对链表进行插入排序
第四天 计数排序
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
计数排序(Counting sort) 是一个 非基于比较 \color{red} {非基于比较} 非基于比较的 稳定 \color{red} {稳定} 稳定的 线性时间 \color{red} {线性时间} 线性时间的排序算法。
- 非基于比较: 之前学的排序都是通过比较数据的大小来实现有序的,比如希尔排序等,而计数排序不用比较数据的大小
计数排序的名字会让我们想到“计数法”,实际上计数排序的实现就是使用的计数法。
1、算法步骤
工作原理: 使用一个额外的数组 cnt,其中第 i 个元素是待排序数组 A 中值等于 i 的元素的个数,然后根据数组 cnt 来将 A 中的元素排到正确的位置。
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
c
void countingSort(char *s){ //计数排序
int cnt[256];
memset(cnt, 0, sizeof(cnt));
for(int i = 0; s[i]; ++i){
cnt[ s[i] ]++;
}
int sSize = 0;
for(int i = 0; i < 256; ++i){
while(cnt[i]){
s[sSize++] = i;
cnt[i]--;
}
}
s[sSize] = '\0';
}
Java
具体实现: 创建一个足够大的数组 cnt,足够大的意思是 cnt 的下标范可以包括所有的待排序数据值。然后遍历待排序数据,使用计数法统计每个数据的出现次数。最后遍历 cnt 数组,将每个值 (cntl) 不为 0 的下标 (i) 放入原数组 cnt[i] 次。
public void countingSort(int[] arr){
int[] cnt = new int[20001]; //(1)
for(int a : arr){
++cnt[a+10000]; //(2)
}
int top = 0;
for(int i = 0; i < cnt.length; i++){
while(cnt[i] != 0) {
arr[top++] = i-10000;
--cnt[i];
}
if(top == arr.length){
return;
}
}
}
(1) 数据个数最大一共有 20000 ,多开一个数组空间防止溢出
(2)偏移量为 10000, 因为负数的最小值为 10000, 加上一个 10000 就可以用 [0,10000] 来表示[-10000,0] 的负数
4、应用
c实现版本
389. 找不同
2733. 既不是最小值也不是最大值
Java实现版本
215. 数组中的第K个最大元素