排序算法——归并排序的相关问题

一、小和问题

问题描述,给定一个数组,如[1, 3, 2, 6, 5],计算每个数左边小于自己的所有数的和,并累加。例如:

1左边没有数

3左边有一个小于自己的数 1

2左边有一个小于自己的数 1

6左边有三个小于自己的数 1 + 3 + 2 = 6

5左边有三个小于自己的数 1 + 3 + 2 = 6

最后 1 + 1 + 6 + 6 = 14,上面给定数组 [1, 3, 2, 6, 5] 的小和解就是 14.

解题思路:这道题的常规思路是循环每个数,然后再遍历它左边的所有数,只要比自己小,就累加到 sum 上。时间复杂度是 O(N^2)。

如何将它改写成 O(N^logN)复杂度的算法?可以利用递归。

本题中我们要计算每个数左侧小于自己数的和,反过来看,就是每个数算一下右边有几个大于自己的数,然后求和。还是以上面的数组为例:

1右边有四个数大于自己,4 * 1 = 4

3右边有两个数大于自己,2 * 3 = 6

2右边有两个数大于自己,2 * 2 = 4

6右边没有大于自己的数

5右边没有大于自己的数

最后 4 + 6 + 4 = 14,和前一种按照题意原本的规则计算的结果完全一致。

有了这个逆向思路,我们可以在归并排序 merge 的时候,由于merge的左右两组一定是有序的,在左组数较小并拷贝时计算右组中大于自己的数的个数乘以自身就,就可以得到一个当前范围内右侧累计值:

上图是一个常规的非对称情况的 merge 操作,其中 1 3 已经通过merge排好序(尽管已经是有序的,但也会执行一次merge)。

在merge的时候,左组拷贝产生小和,右组拷贝不产生小和。根据之前的逆向思路,我们需要计算每个数右组比自己大的元素个数,由于已经有了“左右两组各自有序”这个前提,因此在copy左组元素的时候,直接取右组当前比较的位置到 R 的元素个数即可,而当左右两组当前比较的元素相等时,我们必须先拷贝右组元素,这是为了方便计算右组有几个数大于左组:

实现及测试完整代码:

/*** 小和问题*/
public class Code2_SmallSum {public static int smallSum(int[] arr) {if (arr == null || arr.length < 2)return 0;return process(arr, 0, arr.length - 1);}private static int process(int[] arr, int L, int R) {if (L == R)return 0;int M = L + ((R - L) >> 1);int leftSum = process(arr, L, M);int rightSum = process(arr, M + 1, R);int mergeSum = merge(arr, L, M, R);return leftSum + rightSum + mergeSum;}private static int merge(int[] arr, int L, int M, int R) {int[] help = new int[R - L + 1];int p1 = L;int p2 = M + 1;int i = 0;int res = 0;while (p1 <= M && p2 <= R) {res += arr[p1] < arr[p2] ? (R - p2 + 1) * arr[p1] : 0;help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];}while (p1 <= M) {help[i++] = arr[p1++];}while (p2 <= R) {help[i++] = arr[p2++];}for (i = 0; i < help.length; i++) {arr[L + i] = help[i];}return res;}// for testpublic static int comparator(int[] arr) {if (arr == null || arr.length < 2) {return 0;}int res = 0;for (int i = 1; i < arr.length; i++) {for (int j = 0; j < i; j++) {res += arr[j] < arr[i] ? arr[j] : 0;}}return res;}// for testpublic static int[] generateRandomArray(int maxSize, int maxValue) {int[] arr = new int[(int) ((maxSize + 1) * Math.random())];for (int i = 0; i < arr.length; i++) {arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());}return arr;}// for testpublic static int[] copyArray(int[] arr) {if (arr == null) {return null;}int[] res = new int[arr.length];for (int i = 0; i < arr.length; i++) {res[i] = arr[i];}return res;}// for testpublic static boolean isEqual(int[] arr1, int[] arr2) {if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {return false;}if (arr1 == null && arr2 == null) {return true;}if (arr1.length != arr2.length) {return false;}for (int i = 0; i < arr1.length; i++) {if (arr1[i] != arr2[i]) {return false;}}return true;}// for testpublic static void printArray(int[] arr) {if (arr == null) {return;}for (int i = 0; i < arr.length; i++) {System.out.print(arr[i] + " ");}System.out.println();}// for testpublic static void main(String[] args) {int testTime = 500000;int maxSize = 100;int maxValue = 100;boolean succeed = true;for (int i = 0; i < testTime; i++) {int[] arr1 = generateRandomArray(maxSize, maxValue);int[] arr2 = copyArray(arr1);if (smallSum(arr1) != comparator(arr2)) {succeed = false;printArray(arr1);printArray(arr2);break;}}System.out.println(succeed ? "Nice!" : "Fucking fucked!");}}

二、逆序对个数问题

逆序对个数是经典考题,也是常考题,它的问题是这样的:

给定一个数组,如 [1, 3, 2, 6, 4] ,在整个数组范围内,只要两个数形成降序,即判定为一个逆序对,统计这个数组的逆序对个数:

以上述数组为例,3, 2  和 6,4都是逆序对,因此该数组总共有两个逆序对。

题意非常好理解,同时它也是上题小和问题的一个变种,上面的小和问题通过逆向思维,实际上计算的是右侧有几个比自己大的数,然后累乘自身,而本题是求右侧有多少个比自己小,累加。

那么以升序排序的方式可以求右组比自己大的个数,同理,以降序排序的方式就可以求右组比自己小的个数。因此,只需要降序merge,并统计个数即可,完整代码和测试如下:

/*** 逆序对个数*/
public class Code2_ReversedPair {/*** 降序,再count*/public static int reversedPairCount(int[] arr) {if (arr == null || arr.length < 2)return 0;return process(arr, 0, arr.length - 1);}private static int process(int[] arr, int L, int R) {if (L == R)return 0;int M = L + ((R - L) >> 1);return process(arr, L, M) + process(arr, M + 1, R) + merge(arr, L, M, R);}private static int merge(int[] arr, int L, int M, int R) {int[] help = new int[R - L + 1];int p1 = L;int p2 = M + 1;int i = 0;int count = 0;while (p1 <= M && p2 <= R) {count += arr[p1] > arr[p2] ? (R - p2 + 1) : 0;help[i++] = arr[p1] > arr[p2] ? arr[p1++] : arr[p2++];}while (p1 <= M) {help[i++] = arr[p1++];}while (p2 <= R) {help[i++] = arr[p2++];}for (i = 0; i < help.length; i++) {arr[L + i] = help[i];}return count;}// for testpublic static int comparator(int[] arr) {int ans = 0;for (int i = 0; i < arr.length; i++) {for (int j = i + 1; j < arr.length; j++) {if (arr[i] > arr[j]) {ans++;}}}return ans;}// for testpublic static int[] generateRandomArray(int maxSize, int maxValue) {int[] arr = new int[(int) ((maxSize + 1) * Math.random())];for (int i = 0; i < arr.length; i++) {arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());}return arr;}// for testpublic static int[] copyArray(int[] arr) {if (arr == null) {return null;}int[] res = new int[arr.length];for (int i = 0; i < arr.length; i++) {res[i] = arr[i];}return res;}// for testpublic static boolean isEqual(int[] arr1, int[] arr2) {if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {return false;}if (arr1 == null && arr2 == null) {return true;}if (arr1.length != arr2.length) {return false;}for (int i = 0; i < arr1.length; i++) {if (arr1[i] != arr2[i]) {return false;}}return true;}// for testpublic static void printArray(int[] arr) {if (arr == null) {return;}for (int i = 0; i < arr.length; i++) {System.out.print(arr[i] + " ");}System.out.println();}// for testpublic static void main(String[] args) {int testTime = 500000;int maxSize = 100;int maxValue = 100;System.out.println("测试开始");for (int i = 0; i < testTime; i++) {int[] arr1 = generateRandomArray(maxSize, maxValue);int[] arr2 = copyArray(arr1);if (reversedPairCount(arr1) != comparator(arr2)) {System.out.println("Oops!");printArray(arr1);printArray(arr2);break;}}System.out.println("测试结束");}
}

三、两倍大问题

两倍大问题,给定一个数组,如[6, 7, 1, 3, 2],计算出每个数右侧比自身要小两倍还多的数的累计个数。

/*** 两倍大问题:统计数组中每个比右边几个数两倍还大。* 6 7 1 3 2,* 6比1、2 大两倍还多* 7比1、3、2大两倍还多* 因此总个数就是5个*/
public class Code4_BeggerTwice {public static int biggerTwice(int[] arr) {if (arr == null || arr.length < 2)return 0;int count = process(arr, 0, arr.length - 1);return count;}private static int process(int[] arr, int L, int R) {if (L == R)return 0;int M = L + ((R - L) >> 1);return process(arr, L, M) + process(arr, M + 1, R) + merge(arr, L, M, R);}private static int merge(int[] arr, int L, int M, int R) {int[] help = new int[R - L + 1];int i = 0;int p1 = L;int p2 = M + 1;int count = 0;// [M + 1, R]int windowR = M + 1;for (int j = L; j <= M; j++) {while (windowR <= R && arr[j] <= arr[windowR] * 2)windowR++;count += R - windowR + 1;}while (p1 <= M && p2 <= R) {help[i++] = arr[p1] > arr[p2] ? arr[p1++] : arr[p2++];}while (p1 <= M) {help[i++] = arr[p1++];}while (p2 <= R) {help[i++] = arr[p2++];}for (i = 0; i < help.length; i++) {arr[L + i] = help[i];}return count;}// for testpublic static int comparator(int[] arr) {int ans = 0;for (int i = 0; i < arr.length; i++) {for (int j = i + 1; j < arr.length; j++) {if (arr[i] > (arr[j] << 1)) {ans++;}}}System.out.println("arr2总数为:" + ans);return ans;}// for testpublic static int[] generateRandomArray(int maxSize, int maxValue) {int[] arr = new int[(int) ((maxSize + 1) * Math.random())];for (int i = 0; i < arr.length; i++) {arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) ((maxValue + 1) * Math.random());}return arr;}// for testpublic static int[] copyArray(int[] arr) {if (arr == null) {return null;}int[] res = new int[arr.length];for (int i = 0; i < arr.length; i++) {res[i] = arr[i];}return res;}// for testpublic static boolean isEqual(int[] arr1, int[] arr2) {if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {return false;}if (arr1 == null && arr2 == null) {return true;}if (arr1.length != arr2.length) {return false;}for (int i = 0; i < arr1.length; i++) {if (arr1[i] != arr2[i]) {return false;}}return true;}// for testpublic static void printArray(int[] arr) {if (arr == null) {return;}for (int i = 0; i < arr.length; i++) {System.out.print(arr[i] + " ");}System.out.println();}// for testpublic static void main(String[] args) {int testTime = 500000;int maxSize = 10;int maxValue = 10;System.out.println("测试开始");for (int i = 0; i < testTime; i++) {int[] arr1 = generateRandomArray(maxSize, maxValue);
//            System.out.println("原始数组:==========");
//            printArray(arr1);int[] arr2 = copyArray(arr1);if (biggerTwice(arr1) != comparator(arr2)) {System.out.println("Oops!");printArray(arr1);printArray(arr2);break;}}System.out.println("测试结束");}}

四、求区间和子数组个数

本题是LeetCode真题——327题区间和个数:https://leetcode-cn.com/problems/count-of-range-sum/

提议描述:给定一个数组 arr,两个整数 lower 和 upper,计算出 arr 中有多少个子数组的累加和在 [lower, upper] 范围上,要求子数组必须连续。

例如,[1, 3, -2] ,求累加和范围在 [2, 4] 范围上的子数组个数:

[1] 不符合

[1, 3] 符合

[1, 3, -2] 符合

[3] 符合

[3, -2] 不符合

[-2] 不符合

所以,符合达标的子数组分别是[1, 3]、[1, 3, -2]、[3] ,共 3 个子数组。

小提示:枚举子数组的方式有两种,一种是以头位置开始,上面就是这种方式,还有一种是以尾为标准,例如:

以 0 位置结尾的子数组:[1]

以 1 位置结尾的子数组:[1, 3]、[3]、

以 2 位置结尾的子数组:[1,3,-2]、[3, -2]、[-2]

解题思路:这是一道困难难度的面试题。在解答本题之前,需要有一些转换技巧作为铺垫——前缀和问题

前缀和问题的描述是,一个数组 arr[] ,元素是无序整数,正负0都有,给定一个 [i, j] 范围,求累加和。这个问题可以算得上是区间和问题的一个重要逻辑单元。

最傻瓜式的方式无非就是从 i 到 j 遍历元素,然后累加。如何优化?可以转变成“前缀和问题”。

求[0, j] 累加和,减去[0,i - 1] 范围的累加和,即可得到 [i, j] 范围的累加和。我们称之为“前缀和”,就是因为每个位置的前缀和都是从0位置开始一直加到自身位置,而每个位置上的前缀和只需要遍历一遍就可以轻松得出。

如果我们实现通过一次遍历得到了和原数组每个位置元素对应的前缀和,那么 [i, j] 累加和问题就可以轻松变为从前缀和数组中取 j 和 i 位置上的两个数,然后相减,抛去只执行一次的前缀和数组的生成,后面的每次操作都是常数时间复杂度,极大地提升了效率。

    public static int rangeSum(int[] arr, int lower, int upper) {if (arr == null || lower < 0 || upper >= arr.length)throw new IllegalArgumentException("参数不合法");int[] preSum = new int[arr.length];preSum[0] = arr[0];for (int i = 1; i < arr.length; i++) {preSum[i] = preSum[i - 1] + arr[i];}return preSum[upper] - preSum[lower - 1];}

有了以上这些知识的铺垫,再来回看原题,求累加和属于 [lower, upper] 范围的子数组个数,如果有了原数组对应的前缀和数组,假设以 i 位置为例,求以 i 位置结尾的子数组累加和属于 [lower, upper] 范围,对应前缀和数组,就是求以 i 位置结尾的前缀和与多少个左侧前缀和相减的差属于[lower, upper] ,如果 i 位置上的前缀和为 x,那么这个 i 位置左侧的小前缀和的区间范围就应该落在与其互补的 [x - upper, x - lower] 范围上,简言之就是求前缀和数组上,i 位置左侧有多少个元素属于 [x-upper, x - lower] 范围,由于我们一定可以通过以各个位置上的数结尾的子数组穷举全部子数组,因此利用归并求出每个 i 位置左侧达标的元素并计数,最终就一定会统计出全部达标的子数组个数。

上图中,移位换项指的是  i - j = [lower, upper] ,将 j 移到一侧,得出 j = [i - lower, i - upper] 。

完整代码如下: 

public class Code1_CountOfRangeSum {public static int countRangeSum(int[] arr, int lower, int upper) {if (arr == null || arr.length == 0)throw new IllegalArgumentException("参数非法!");long[] preSum = new long[arr.length];preSum[0] = arr[0];for (int i = 1; i < arr.length; i++) {preSum[i] = preSum[i - 1] + arr[i];}return process(preSum, 0, preSum.length - 1, lower, upper);}private static int process(long[] preSum, int L, int R, int lower, int upper) {if (L == R)return preSum[L] >= lower && preSum[L] <= upper ? 1 : 0;int M = L + ((R - L) >> 1);return process(preSum, L, M, lower, upper)+ process(preSum, M + 1, R, lower, upper)+ merge(preSum, L, M, R, lower, upper);}private static int merge(long[] preSum, int L, int M, int R, int lower, int upper) {int count = 0;int windowL = L;int windowR = L;// [windowL, windowR)for (int i = M + 1; i <= R; i++) {long min = preSum[i] - upper;long max = preSum[i] - lower;while (windowR <= M && preSum[windowR] <= max)windowR++;while (windowL <= M && preSum[windowL] < min)windowL++;count += windowR - windowL;}long[] help = new long[R - L + 1];int i = 0;int p1 = L;int p2 = M + 1;while (p1 <= M && p2 <= R) {help[i++] = preSum[p1] <= preSum[p2] ? preSum[p1++] : preSum[p2++];}while (p1 <= M)help[i++] = preSum[p1++];while (p2 <= R)help[i++] = preSum[p2++];for (i = 0; i < help.length; i++) {preSum[L + i] = help[i];}return count;}
}

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/558663.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

经典数据结构——堆的实现

一、完全二叉树 堆是一种完全二叉树&#xff0c;什么是完全二叉树&#xff1f; 简单的说&#xff0c;一棵满二叉树表示的是所有节点全部饱和&#xff0c;最后一层全部占满&#xff1a; 而完全二叉树指的是满二叉树的最后一层&#xff0c;所有叶子节点都从左往顺序排满&#x…

排序算法 —— 堆排序

引言 此文基于《经典数据结构——堆的实现》中堆结构&#xff0c;实现一个以堆处理排序的算法。 一、算法思想 基于堆结构的堆排序的算法思想非常简单&#xff0c;循环获取大根堆中的最大值&#xff08;0位置的根节点&#xff09;放到堆的末尾&#xff0c;直到将堆拿空。 由…

经典数据结构——前缀树

引言 前缀树——trie /ˈtraɪ//树&#xff0c;也叫作“单词查找树”、“字典树”。 它属于多叉树结构&#xff0c;典型应用场景是统计、保存大量的字符串&#xff0c;经常被搜索引擎系统用于文本词频统计。它的优点是利用字符串的公共前缀来减少查找时间&#xff0c;最大限度…

排序算法 —— 计数排序

引言 计数排序是桶排序思想的一种具体实现&#xff0c;针对一些具有特殊限制的样本数据&#xff0c;如公司员工年龄&#xff0c;那么样本数据本身就一定在0~200之间&#xff0c;针对这样的数据&#xff0c;使用从0到200 的桶数组&#xff0c;桶的位置已经是有序的&#xff0c;…

Java多线程 —— 线程状态迁移

引言 线程状态迁移&#xff0c;又常被称作线程的生命周期&#xff0c;指的是线程从创建到终结需要经历哪些状态&#xff0c;什么情况下会出现哪些状态。 线程的状态直接关系着并发编程的各种问题&#xff0c;本文就线程的状态迁移做一初步探讨&#xff0c;并总结在何种情况下…

Java中的Unsafe

Java和C语言的一个重要区别就是Java中我们无法直接操作一块内存区域&#xff0c;不能像C中那样可以自己申请内存和释放内存。Java中的Unsafe类为我们提供了类似C手动管理内存的能力。 Unsafe类&#xff0c;全限定名是sun.misc.Unsafe&#xff0c;从名字中我们可以看出来这个类对…

arm中断保护和恢复_浅谈ARM处理器的七种异常处理

昨天的文章&#xff0c;我们谈了ARM处理器的七种运行模式&#xff0c;分别是&#xff1a;用户模式User(usr)&#xff0c;系统模式System(sys)&#xff0c;快速中断模式(fiq)&#xff0c;管理模式Supervisor(svc)&#xff0c;外部中断模式(irq)&#xff0c;数据访问中止模式Abor…

Queue —— JUC 的豪华队列组件

目录引言一、Queue 的继承关系1.1 Queue 定义基础操作1.2 AbstractQueue 为子类减负1.3 BlockingQueue 阻塞式Queue1.4 Deque 两头进出二、Queue 的重要实现三、BlockingQueue 的实现原理四、Queue 在生产者消费者模式中的应用五、Queue 在线程池中的应用六、ConcurrentLinkedQ…

daad转换器实验数据_箔芯片电阻在高温应用A/D转换器中的应用

工业/应用领域高温&#xff1a;地震数据采集系统、石油勘探监测、高精度检测仪产品采用&#xff1a;V5X5 Bulk Metal (R) Foil芯片电阻案例介绍TX424是一个完整的4通道24位模数转换器&#xff0c;采用40脚封装。该设计采用最先进设计方案&#xff0c;两个双通道24位调节器和一个…

excel分段排序_学会这个神操作,报表填报不再五花八门,效率远超Excel

在报表工作人员的的日常工作中&#xff0c;常常要面临统计混乱的终端用户输入的问题。由于无法准确限制用户的输入内容&#xff0c;所以在最终进行数据统计时&#xff0c;常常会出现数据不合法的情况。为此需要花费大量的人力和时间核对校验数据。举个简单的例子&#xff0c;某…

IDEA——必备插件指南

目录一、Free-Mybatis-Plugin二、Lombok三、jclasslib Bytecode Viewer一、Free-Mybatis-Plugin 二、Lombok 三、jclasslib Bytecode Viewer 学习 class 文件的必备插件。 使用简单&#xff0c;安装后可以在菜单 View 中看到 show bytecode with jclasslib&#xff1a; 效果…

jitter 如何优化网络_如何做好关键词优化网络?

越来越多的传统企业开始建立自己的网站&#xff0c;进而不断的推广自己的产品。为了能够让自己的企业网站出现在搜索引擎的首页&#xff0c;现在最常用的手段就是竞价排名和关键词优化网络。往往很多企业会选择关键词优化网络这种方式来推广自己的网站&#xff0c;对于新手seoe…

python学生名片系统_Python入门教程完整版400集(懂中文就能学会)快来带走

如何入门Python&#xff1f;权威Python大型400集视频&#xff0c;学了Python可以做什么&#xff1f;小编今天给大家分享一套高老师的python400集视频教程&#xff0c;里面包含入门进阶&#xff0c;源码&#xff0c;实战项目等等&#xff0c;&#xff0c;不管你是正在学习中&…

JVM——详解类加载过程

导航一、过程概述二、Loading2.1 类加载器2.2 双亲委派机制2.3 类在内存中的结构三、Linking四、Initializing一、过程概述 java 源文件编译后会生成一个 .class文件存储在硬盘上。 在程序运行时&#xff0c;会将用到的类文件加载到 JVM 内存中。从磁盘到内存的过程总共分为三…

下载 Java 学习的权威文档

JVMS 和 JLS 文档的下载 快速直达&#xff1a; https://docs.oracle.com/javase/8/ --> Java Language and Virtual Machine Specifications jvm specification 和 java language specification 是Java 学习的两个最权威的文档。如果你用的是 Java 8&#xff0c;就可以去下载…

iso图像测试卡_4700万像素 五轴防抖 徕卡正式发布SL2无反相机

出自蜂鸟网-器材频道&#xff0c;原文链接&#xff1a;https://m.fengniao.com/document/5358989.html徕卡于今日正式发布SL2相机&#xff0c;搭载4700万像素CMOS感光元件、通过感光元件移位实现光学图像稳定的五轴防抖技术、全新徕卡物距探测式自动对焦技术以及576万像素分辨率…

JVM——对象的创建与内存布局

导航一、对象的创建过程二、对象的内存布局2.1 内存布局2.2 计算对象的内存大小三、对象的定位3.1 句柄池3.2 直接指针四、对象的分配过程一、对象的创建过程 对象&#xff0c;又叫实例&#xff0c;是 OOP 的最常用角色。 如何创建一个对象&#xff1f;一般都是使用 new 关键…

JVM垃圾收集器——G1

导航引言一、G1 介绍1.1 适用场景1.2 设计初衷1.3 关注焦点1.4 工作模式1.5 堆的逻辑结构1.6 主要收集目标1.7 停顿预测模型1.8 拷贝和压缩1.9 与 CMS 和 Parallel 收集器的比较1.10 固定停顿目标二、堆的逻辑分区2.1 region2.2 CSet2.3 RSet2.4 Card Table三、G1 的工作原理3.…

的mvc_简述PHP网站开发的MVC模式

为了提高开发时候的代码重用和开发速度&#xff0c;php使用了mvc的模式&#xff0c;主要是对代码的功能进行了分类&#xff0c;M&#xff1a;model主要是对数据库进行操作&#xff0c;v&#xff1a;view主要是前端html文件操作&#xff0c;c&#xff1a;controller主要是编写基…

CAP 原则与 BASE 理论

导航引言一、CAP 原则1.1 Consistency 一致性1.2 Available 可用性1.3 Partition tolerance 分区容错性1.4 CAP 的矛盾1.5 CAP 的组合场景二、BASE 理论2.1 基本可用2.2 软状态2.3 最终一致性2.3.1 因果一致性2.3.2 读自身所写2.3.3 会话一致性2.3.4 单调读一致性2.3.5 单调写一…