这是我参与「第三届青训营 -后端场」笔记创作活动的第3篇笔记。
1.算法经典应用场景
例子:抖音直播排行榜功能,某个时间段内,直播间礼物数TOP10房间获得奖励,需要在每个房间展示
具体实现:
- 礼物数量存储在Redis-zset中,使用skiplist 使得元素整体有序
- 使用Redis集群,避免单机压力过大,使用主从算法、分片算法
- 保证集群原信息的稳定,使用一-致性算法(raft)
- 后端使用缓存算法(LRU)降低Redis压力,展示房间排行榜,也就是不用做到实时刷新
2.经典排序算法
1.插入排序
将元素不断插入已经排好顺序的array中,从后向前找到目标位置,时间复杂度O(n^2)
public static int[] insertionSort(int[] a){
if(a.length<=1) return a;
for(int i = 1; i<a.length; ++i){
int value = a[i];
int j = i-1;
//从后向前,查找第一个小于等于value的位置
for(; j >= 0; --j){
if(a[j] > value){
a[j+1] = a[j]; //大于目标值向后搬
}else{
break;
}
}
a[j+1] = value;
}
return a;
}
2.快速排序
分治思想,不断分割序列直到序列整体有序,最快时间复杂度:O(N*logN) 最差:O(N2)
- 选定一个pivot(轴点)
- 使用pivot分割序列,分成元素比pivot大 和 元素比pivot小的两个序列
//快速排序
public static int[] quickSort(int[] arr){
//洗牌避免出现耗时的极端情况
shuffle(arr);
//对数组进行排序
process(arr,0,arr.length-1);
return arr;
}
//洗牌过程
private static void shuffle(int[] arr){
Random rand=new Random();
int n=arr.length;
for(int i=0;i<n;i++){
int r=i+ rand.nextInt(n-i);
swap(arr,i,r);
}
}
//交换数组
private static void swap(int[] arr,int left,int right){
int temp=arr[left];
arr[left]=arr[right];
arr[right]=temp;
}
private static void process(int[] arr,int left,int right){
if(left>=right) return;
//寻找一个轴点,左边的都比他小,右边的都比他大
int pivot=participate(arr,left,right);
process(arr,left,pivot-1);
process(arr,pivot+1,right);
}
private static int participate(int[] arr,int left,int right){
int num=arr[left];
int i=left+1;
int j=right;
while(i<=j){
while(i<right && arr[i]<=num){
i++;
}
while(j>left && arr[j]>num){
j--;
}
//此时说明所有的数已经排序完毕,大数放在后面,小数放在前面
if(i>=j) break;
//把所有比num小的数放在前面,把所以比num大的数放在后面
swap(arr,i,j);
}
swap(arr,left,j);
return j;
}
3.堆排序
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
- 大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
- 小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2] 堆排序步骤:
a.将无序序列构建成一个堆,根据升序降序需求选择大顶堆(升序)或小顶堆(降序);
b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端,目的是将最大的数放在数组最后,接下来只需要排序前面的数字即可;
c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
public static void sort(int []arr){
//1.构建大顶堆
for(int i=arr.length/2-1;i>=0;i--){
//从第一个非叶子结点从下至上,从右至左调整结构
adjustHeap(arr,i,arr.length);
}
//2.调整堆结构+交换堆顶元素与末尾元素
for(int j=arr.length-1;j>0;j--){
swap(arr,0,j);//将堆顶元素与末尾元素进行交换
adjustHeap(arr,0,j);//重新对堆进行调整
}
}
//调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
public static void adjustHeap(int []arr,int i,int length){
int temp = arr[i];//先取出当前元素i
for(int k=i*2+1;k<length;k=k*2+1){//从i结点的左子结点开始,也就是2i+1处开始
if(k+1<length && arr[k]<arr[k+1]){//如果左子结点小于右子结点,k指向右子结点
k++;
}
if(arr[k] >temp){//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
arr[i] = arr[k];
i = k;
}else{
break;
}
}
arr[i] = temp;//将temp值放到最终的位置
}
//交换元素
public static void swap(int []arr,int a ,int b){
int temp=arr[a];
arr[a] = arr[b];
arr[b] = temp;
}