diff --git a/工作日志/2021年3月18日-今日计划.md b/工作日志/2021年3月18日-今日计划.md index 9e200baf..1f9e75c7 100644 --- a/工作日志/2021年3月18日-今日计划.md +++ b/工作日志/2021年3月18日-今日计划.md @@ -4,7 +4,7 @@ - [ ] 学习、复习图算法,动手实现所有的图算法。 - [ ] 看完数据结构与算法的三本书!!!对相关的原理进行复习和总结。 -- [ ] 学习机器学习的实现方案。毕设计划真正的开始执行。 +- [x] 学习机器学习的实现方案。毕设计划真正的开始执行。 - [ ] 关于字符串分割。字符串格式化方法的总结。转换成流,作为流对象处理。转换为容器。作为容器对象处理,使用泛型算法。 ## 收获 diff --git a/工作日志/2021年3月26日-开组会.md b/工作日志/2021年3月26日-开组会.md index 4892b3d4..7069e6b5 100644 --- a/工作日志/2021年3月26日-开组会.md +++ b/工作日志/2021年3月26日-开组会.md @@ -1,4 +1,14 @@ +# 今日计划 +## 计划 + +* 执行之前的计划 +- [ ] 整理会议记录发到群里。 + +## 收获 + + +# PPT准备 ## 目标 ## 工程 diff --git a/数据结构/3.1 单调栈.md b/数据结构/3.1 单调栈.md new file mode 100644 index 00000000..e69de29b diff --git a/数据结构/6.10 树状数组.md b/数据结构/6.10 树状数组.md new file mode 100644 index 00000000..6f4544d8 --- /dev/null +++ b/数据结构/6.10 树状数组.md @@ -0,0 +1,114 @@ +# 树状数组Binary Tree Array + + +## 1 概念 + + +### lowbit运算 +$$ +lowbit(x) = x \& ((\sim x)+1) +$$ +* 作用:二进制表示中,保留最后的`1`。如`x=10100100`,`lowbit(x)=00000100` + +### 树状数组 + +* 根据lowbit的运算规则构建树状数组。 +* 其中数组A第i个位置的值**管理区间**[i-lowbit(i)+1,i]。即去掉二进制最后一位后到当前位置的的区间。 + +$$ +A[i] = \sum_{k=i-lowbit(i)+1}^i A[k] +$$ + +* 其中数组A第i个位置的值的**被管节点**。即找到所有的之后的$2^i$的点,也包括非$2^i$的点。 + +$$ +i = i + lowbit(i) ,imax截止 + +### 求前缀区间的值 + +> 所有lowbit()对应的值相加。 + +* 那么对于任意的x,sum(1,x)怎么求呢?我们把最终得到的答案存在ans变量中,执行下面的操作: + 1. sum=0 + 2. sum += A[i] + 3. i = i-lowbit(i) + 4. 重复2-3步骤,直到i<0截止 + + +### 求区间的值 + +* 询问区间[L,R]的和sum(L,R)。我们只需要求出sum(1,R)和sum(1,L-1),然后sum(1,R)-sum(1,L-1)就是sum(L,R). +$$ +sum(L,R)=sum(1,R)-sum(1,L) +$$ + +## 实例1 基本运算 + +```C++ +//求最小幂2^k: +int Lowbit(int t) +{ + return t & ( t ^ ( t - 1 ) ); +} + +//求前n项和: +int Sum(int end) +{ + int sum = 0; + while(end > 0) + { + sum += in[end]; + end -= Lowbit(end); + } + return sum; +} + +//对某个元素进行加法操作 +void plus(int pos , int num) +{ + while(pos <= n) + { + in[pos] += num; + pos += Lowbit(pos); + } +} +``` + +## 实例2 逆序对 diff --git a/数据结构/6.11 线段树.md b/数据结构/6.11 线段树.md new file mode 100644 index 00000000..397393be --- /dev/null +++ b/数据结构/6.11 线段树.md @@ -0,0 +1,234 @@ +# 线段树 + +## 1 线段树的概念 + +### 概述 +* 线段树(Segment Tree)也是一棵树,只不过元素的值代表一个区间。常用区间的 统计 操作,比如一个区间的 **最大值(max),最小值(min),和(sum)** 等等 + +* 如一个长度为10的数组,它对应的 求和 线段树,如下图所示(图中的数字表示索引): + +![](image/2021-03-26-18-52-43.png) + +### 定义 +* 线段树是一个平衡二叉树,但不一定是完全二叉树。 + * 根节点就是 0~lenght-1 的和 + * 根节点的左右子树平分根节点的区间 + * 然后依次类推,直到只有一个元素不能划分为止,该元素也就是二叉树的叶子节点。 + +### 复杂度 +* 时间复杂度为O(log n) +* 空间复杂度为O(n) + +## 2 线段树的基本操作 +### 构建线段树 + +* 根据上面我们对线段树的描述,构建一个线段树就比较简单了,根节点就是整个区间,根节点的左右子树平分根节点的区间,直至区间内只剩下一个元素不能平分为止。如下面递归的伪代码: +```C++ +private void buildSegmentTree(int treeIndex, int treeLeft, int treeRight) { + //如果区间内只剩下一个元素 + if (treeLeft == treeRight) { + tree[treeIndex] = data[treeLeft]; + return; + } + //当前节点左子树索引 + int leftTreeIndex = getLeft(treeIndex); + //当前节点右子树索引 + int rightTreeIndex = getRight(treeIndex); + //int mid = (left+right)/2; + int mid = treeLeft + (treeRight - treeLeft) / 2; + //构建左子树 + buildSegmentTree(leftTreeIndex, treeLeft, mid); + //构建右子树 + buildSegmentTree(rightTreeIndex, mid + 1, treeRight); + //当前节点存放的值,根据具体业务,如果求和就是两个值相加 + //如果是求最大值,那么就存放最大值 + tree[treeIndex] = tree[leftTreeIndex] + tree[rightTreeIndex] + +} +``` + +* 对下面一个数组 +![](image/2021-03-26-19-16-09.png) + + +* 就会构建成如下一个线段树(图中括号里数字表示索引区间) + +![](image/2021-03-26-19-16-22.png) + + +### 修改线段树 + +* 针对上面的数组,把索引为 1 的值改成 6 如下图所示 + +![](image/2021-03-26-19-17-04.png) + + +* 那么线段树需要修改的节点有(虚线标明): + +![](image/2021-03-26-19-17-16.png) + +### 线段树的查询 +* 对于线段树的查询,主要有以下几种情况: + * 要查询的区间在刚好就是当前节点的区间 + * 要查找的区间在当前节点的左子树区间 + * 要查找的区间在当前节点的右子树区间 + * 要查找的区间一部分在当前节点的左子树区间,一部分在右子树区间 + +## 3 实现一个线段树 +* 下面实现的线段树,有三个功能: + * 把数组构建成一颗线段树 + * 线段树的修改 + * 线段树的查询 +```java +public class ArraySegmentTree { + + private T tree[]; + private T data[]; + + private Merger merger; + + public interface Merger { + T merge(T a, T b); + } + + public ArraySegmentTree(T[] arr, Merger merger) { + this.merger = merger; + data = (T[]) new Object[arr.length]; + for (int i = 0; i < data.length; i++) { + data[i] = arr[i]; + } + + this.tree = (T[]) new Object[data.length * 4]; + buildSegmentTree(0, 0, data.length - 1); + + } + + + /** + * 构建线段树 + * + * @param treeIndex 当前需要添加节点的索引 + * @param treeLeft treeIndex左边界 + * @param treeRight treeIndex右边界 + */ + private void buildSegmentTree(int treeIndex, int treeLeft, int treeRight) { + if (treeLeft == treeRight) { + tree[treeIndex] = data[treeLeft]; + return; + } + //当前节点左子树索引 + int leftTreeIndex = getLeft(treeIndex); + //当前节点右子树索引 + int rightTreeIndex = getRight(treeIndex); + //int mid = (left+right)/2; 如果left和right很大,可能会导致整型溢出 + int mid = treeLeft + (treeRight - treeLeft) / 2; + //构建左子树 + buildSegmentTree(leftTreeIndex, treeLeft, mid); + //构建右子树 + buildSegmentTree(rightTreeIndex, mid + 1, treeRight); + //当前节点存放的值 + tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]); + + } + + public T query(int start, int end) { + return query(0, 0, data.length - 1, start, end); + } + + /** + * @param treeIndex 当前查找的节点 + * @param treeLeft treeIndex的左边界 + * @param treeRight treeIndex的右边界 + * @param queryL 用户需要查找的左边界 + * @param queryR 用户需要查找的右边界 + * @return + */ + private T query(int treeIndex, int treeLeft, int treeRight, int queryL, int queryR) { + + //1, 需要查找的范围完刚好在这个treeIndex节点的区间 + if (treeLeft == queryL && treeRight == queryR) { + return tree[treeIndex]; + } + + //当前节点的区间的中间点 + int mid = treeLeft + (treeRight - treeLeft) / 2; + //左子树索引 + int leftTreeIndex = getLeft(treeIndex); + //右子树索引 + int rightTreeIndex = getRight(treeIndex); + + + //2, 需要查找的范围完全在左子树的区间里 + if (queryR <= mid) { + return query(leftTreeIndex, treeLeft, mid, queryL, queryR); + } + //3, 需要查找的范围完全在右子树区间里 + if (queryL >= mid + 1) { + return query(rightTreeIndex, mid + 1, treeRight, queryL, queryR); + } + + //需要查找的范围一部分在左子树里,一部分在右子树中 + T left = query(leftTreeIndex, treeLeft, mid, queryL, mid); + T right = query(rightTreeIndex, mid + 1, treeRight, mid + 1, queryR); + return merger.merge(left, right); + } + + + public void update(int index, T e) { + data[index] = e; + update(0, 0, data.length - 1, index, e); + } + + + private void update(int treeIndex, int treeLeft, int treeRight, int index, T e) { + if (treeLeft == treeRight) { + tree[treeIndex] = e; + return; + } + + int mid = treeLeft + (treeRight - treeLeft) / 2; + int leftChildIndex = getLeft(treeIndex); + int rightChildIndex = getRight(treeIndex); + + if (index <= mid) { + update(leftChildIndex, treeLeft, mid, index, e); + } else if (index >= mid + 1) { + update(rightChildIndex, mid + 1, treeRight, index, e); + } + + //更改完叶子节点后,还需要对他的所有祖辈节点更新 + tree[treeIndex] = merger.merge(tree[leftChildIndex], tree[rightChildIndex]); + } + + public T get(int index) { + return data[0]; + } + + public int size() { + return data.length; + } + + public int getLeft(int index) { + return index * 2 + 1; + } + + public int getRight(int index) { + return index * 2 + 2; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("["); + for (int i = 0; i < tree.length; i++) { + if (tree[i] == null) { + continue; + } + builder.append(tree[i]).append(','); + } + builder.deleteCharAt(builder.length() - 1); + builder.append(']'); + return builder.toString(); + } +} +``` \ No newline at end of file diff --git a/数据结构/10 字典树.md b/数据结构/6.13 字典树.md similarity index 100% rename from 数据结构/10 字典树.md rename to 数据结构/6.13 字典树.md diff --git a/数据结构/6.9 特异树.md b/数据结构/6.15 特异树.md similarity index 100% rename from 数据结构/6.9 特异树.md rename to 数据结构/6.15 特异树.md diff --git a/数据结构/6.9 差分数组.md b/数据结构/6.9 差分数组.md new file mode 100644 index 00000000..1089fe56 --- /dev/null +++ b/数据结构/6.9 差分数组.md @@ -0,0 +1,110 @@ +# 差分数组Sparse Array + + +## 1 定义 + +* 定义:原数组为a,差分数组为d,那么有 +$$ +d[i] = a[i] - a[i - 1] +$$ + +![](image/2021-03-26-17-36-48.png) + +* 其实差分数组是一个**辅助数组**,从侧面来表示给定某一数组的变化,一般用来对数组进行区间修改的操作 + +## 2 性质 +1. a[i]等于d[i]的前缀和 + +$$ +a[i] = \sum_{0}^i d_i +$$ + +2. d[i]等于a[i]两个临近元素的差 +$$ +d[i] = a[i] - a[i - 1] +$$ + +## 3 应用 +### 区间修改 + +* 当对一个区间进行增减某个值的时候,他的差分数组对应的区间左端点的值会同步变化,而他的右端点的后一个值则会相反地变化。他们的差分数组其实是不会变化的。 + +* 例如:将区间【1,4】的数值全部加上3 + +![](image/2021-03-26-17-47-36.png) + +* [l,r]区间内的数加k可以表示为如下形式: + +$$ +d[l]+k\\ +d[r+1]-k +$$ + +### 元素求值 +* 既然我们要对区间进行修改,那么差分数组的作用一定就是求多次进行区间修改后的数组。 +* 直接反过来即得 +$$ +a[i]=a[i-1]+d[i] +$$ + +## 实例1 区间涂色 + + +### 问题描述 + +* N个气球排成一排,从左到右依次编号为1,2,3....N.每次给定2个整数a b(a <= b),lele便为骑上他的“小飞鸽"牌电动车从气球a开始到气球b依次给每个气球涂一次颜色。但是N次以后lele已经忘记了第I个气球已经涂过几次颜色了,你能帮他算出每个气球被涂过几次颜色吗? + +### 问题分析 + +* 多次区间修改后求数组的值。 + + +### 算法实现 + +```C++ +#include +#include +#include +#include +#include +#include +#define ll long long +#define mem(a,b) memset(a,b,sizeof(a)) +using namespace std; +const int inf=0x3f3f3f3f; +const int mm=1e5+10; + +int a[mm],b[mm]; +int x,y; +int main() +{ + int n; + while(scanf("%d",&n)&&n){ + mem(a,0); + mem(b,0); + for(int i=1;i<=n;i++){ + scanf("%d%d",&x,&y); + b[x]++; + b[y+1]--; + } + for(int i=1;i<=n;i++) + a[i]=a[i-1]+b[i]; + + for(int i=1;i **二分查找针对的是一个有序的数据集合,查找思想有点类似分治思想。每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为 0**。 - -**存储结构** - -使用二分查找需要两个前提: - -(1) 必须是**顺序**存储结构。 - -(2) 必须是**有序**的表。 - -**基本思想** - -首先,将表**中间位置**记录的关键字与查找关键字比较,如果两者相等,则查找成功; - -否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。 -重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。 - -**核心代码** - -```java -public int binarySearch(int[] list, int length, int key) { - int low = 0, mid = 0, high = length - 1; - while (low <= high) { - mid = (low + high) / 2; - if (list[mid] == key) { - return mid; // 查找成功,直接返回位置 - } else if (list[mid] < key) { - low = mid + 1; // 关键字大于中间位置的值,则在大值区间[mid+1, high]继续查找 - } else { - high = mid - 1; // 关键字小于中间位置的值,则在小值区间[low, mid-1]继续查找 - } - } - return -1; -} -``` - -**算法分析** - -**二分查找的过程可看成一个二叉树**。 - -把查找区间的中间位置视为树的根,左区间和右区间视为根的左子树和右子树。 - -由此得到的二叉树,称为二分查找的判定树或比较树。 - -由此可知,二分查找的**平均查找长度**实际上就是树的高度**O(log2N)**。 - -**二分查找的局限性** - -- 二分查找依赖的是顺序表结构,简单点说就是数组 -- 二分查找针对的是有序数据 -- 数据量太小不适合二分查找 -- 数据量太大也不适合二分查找 - -## 分块查找 - -**要点** - -分块查找(Blocking Search)又称**索引顺序查找**。它是一种性能介于顺序查找和二分查找之间的查找方法。 - -分块查找由于只要求索引表是有序的,对块内节点没有排序要求,因此特别适合于节点动态变化的情况。 - -**存储结构** - -分块查找表是由**“分块有序”的线性表**和**索引表**两部分构成的。 - -所谓**“分块有序”的线性表**,是指: - -假设要排序的表为R[0...N-1],**将表均匀分成b块**,前b-1块中记录个数为s=N/b,最后一块记录数小于等于s; - -每一块中的关键字不一定有序,但**前一块中的最大关键字必须小于后一块中的最小关键字**。 - -***注:这是使用分块查找的前提条件。*** - -如上将表均匀分成b块后,抽取各块中的**最大关键字**和**起始位置**构成一个索引表IDX[0...b-1]。 - -由于表R是分块有序的,所以**索引表是一个递增有序表**。 - -下图就是一个分块查找表的存储结构示意图 - -![img](https://upload-images.jianshu.io/upload_images/3101171-b7ad44c68d0c3c75.png) - -**基本思想** - -分块查找算法有两个处理步骤: - -**(1) 首先查找索引表** - -因为分块查找表是“分块有序”的,所以我们可以通过索引表来锁定关键字所在的区间。 - -又因为索引表是递增有序的,所以查找索引可以使用顺序查找或二分查找。 - -**(2) 然后在已确定的块中进行顺序查找** - -因为块中不一定是有序的,所以只能使用顺序查找。 - -**代码范例** - -![img](http://upload-images.jianshu.io/upload_images/3101171-2737612c781e66e8.gif?imageMogr2/auto-orient/strip) - -```java -class BlockSearch { - - class IndexType { - public int key; // 分块中的最大值 - public int link; // 分块的起始位置 - } - - // 建立索引方法,n 是线性表最大长度,gap是分块的最大长度 - public IndexType[] createIndex(int[] list, int n, int gap) { - int i = 0, j = 0, max = 0; - int num = n / gap; - IndexType[] idxGroup = new IndexType[num]; // 根据步长数分配索引数组大小 - - while (i < num) { - j = 0; - idxGroup[i] = new IndexType(); - idxGroup[i].link = gap * i; // 确定当前索引组的第一个元素位置 - max = list[gap * i]; // 每次假设当前组的第一个数为最大值 - // 遍历这个分块,找到最大值 - while (j < gap) { - if (max < list[gap * i + j]) { - max = list[gap * i + j]; - } - j++; - } - idxGroup[i].key = max; - i++; - } - - return idxGroup; - } - - // 分块查找算法 - public int blockSearch(IndexType[] idxGroup, int m, int[] list, int n, int key) { - int mid = 0; - int low = 0; - int high = m -1; - int gap = n / m; // 分块大小等于线性表长度除以组数 - - // 先在索引表中进行二分查找,找到的位置存放在 low 中 - while (low <= high) { - mid = (low + high) / 2; - if (idxGroup[mid].key >= key) { - high = mid - 1; - } else { - low = mid + 1; - } - } - - // 在索引表中查找成功后,再在线性表的指定块中进行顺序查找 - if (low < m) { - for (int i = idxGroup[low].link; i < idxGroup[low].link + gap; i++) { - if (list[i] == key) - return i; - } - } - - return -1; - } - - // 打印完整序列 - public void printAll(int[] list) { - for (int value : list) { - System.out.print(value + " "); - } - System.out.println(); - } - - // 打印索引表 - public void printIDX(IndexType[] list) { - System.out.println("构造索引表如下:"); - for (IndexType elem : list) { - System.out.format("key = %d, link = %d\n", elem.key, elem.link); - } - System.out.println(); - } - - public static void main(String[] args) { - int key = 85; - int array2[] = { 8, 14, 6, 9, 10, 22, 34, 18, 19, 31, 40, 38, 54, 66, 46, 71, 78, 68, 80, 85 }; - BlockSearch search = new BlockSearch(); - - System.out.print("线性表: "); - search.printAll(array2); - - IndexType[] idxGroup = search.createIndex(array2, array2.length, 5); - search.printIDX(idxGroup); - int pos = search.blockSearch(idxGroup, idxGroup.length, array2, - array2.length, key); - if (-1 == pos) { - System.out.format("查找key = %d失败", key); - } else { - System.out.format("查找key = %d成功,位置为%d", key, pos); - } - } - -} -``` - -**运行结果** - -``` -线性表: 8 14 6 9 10 22 34 18 19 31 40 38 54 66 46 71 78 68 80 85 -构造索引表如下: -key = 14, link = 0 -key = 34, link = 5 -key = 66, link = 10 -key = 85, link = 15 - -查找key = 85成功,位置为19 -``` - -**算法分析** - -因为分块查找实际上是两次查找过程之和。若以二分查找来确定块,显然它的查找效率介于顺序查找和二分查找之间。 - -## 三种线性查找的PK - -(1) 以平均查找长度而言,二分查找 > 分块查找 > 顺序查找。 - -(2) 从适用性而言,顺序查找无限制条件,二分查找仅适用于有序表,分块查找要求“分块有序”。 - -(3) 从存储结构而言,顺序查找和分块查找既可用于顺序表也可用于链表;而二分查找只适用于顺序表。 - -(4) 分块查找综合了顺序查找和二分查找的优点,既可以较为快速,也能使用动态变化的要求。 - -## 资源 - -《数据结构习题与解析》(B级第3版) diff --git a/数据结构/image/2021-03-26-17-36-48.png b/数据结构/image/2021-03-26-17-36-48.png new file mode 100644 index 00000000..937f5de0 Binary files /dev/null and b/数据结构/image/2021-03-26-17-36-48.png differ diff --git a/数据结构/image/2021-03-26-17-47-36.png b/数据结构/image/2021-03-26-17-47-36.png new file mode 100644 index 00000000..2346bf7f Binary files /dev/null and b/数据结构/image/2021-03-26-17-47-36.png differ diff --git a/数据结构/image/2021-03-26-18-06-40.png b/数据结构/image/2021-03-26-18-06-40.png new file mode 100644 index 00000000..95c55787 Binary files /dev/null and b/数据结构/image/2021-03-26-18-06-40.png differ diff --git a/数据结构/image/2021-03-26-18-42-22.png b/数据结构/image/2021-03-26-18-42-22.png new file mode 100644 index 00000000..38712765 Binary files /dev/null and b/数据结构/image/2021-03-26-18-42-22.png differ diff --git a/数据结构/image/2021-03-26-18-52-43.png b/数据结构/image/2021-03-26-18-52-43.png new file mode 100644 index 00000000..5afbd8d7 Binary files /dev/null and b/数据结构/image/2021-03-26-18-52-43.png differ diff --git a/数据结构/image/2021-03-26-19-16-09.png b/数据结构/image/2021-03-26-19-16-09.png new file mode 100644 index 00000000..9250b26a Binary files /dev/null and b/数据结构/image/2021-03-26-19-16-09.png differ diff --git a/数据结构/image/2021-03-26-19-16-22.png b/数据结构/image/2021-03-26-19-16-22.png new file mode 100644 index 00000000..74933ec5 Binary files /dev/null and b/数据结构/image/2021-03-26-19-16-22.png differ diff --git a/数据结构/image/2021-03-26-19-17-04.png b/数据结构/image/2021-03-26-19-17-04.png new file mode 100644 index 00000000..a2dbb510 Binary files /dev/null and b/数据结构/image/2021-03-26-19-17-04.png differ diff --git a/数据结构/image/2021-03-26-19-17-16.png b/数据结构/image/2021-03-26-19-17-16.png new file mode 100644 index 00000000..ef681c82 Binary files /dev/null and b/数据结构/image/2021-03-26-19-17-16.png differ diff --git a/算法/A类:基本算法/3.9 位运算算法.md b/算法/A类:基本算法/3.9 位运算算法.md index 22467a8e..a7ac887d 100644 --- a/算法/A类:基本算法/3.9 位运算算法.md +++ b/算法/A类:基本算法/3.9 位运算算法.md @@ -4,20 +4,20 @@ ### 位运算 -符号|说明 -|----|----| -& | 按位与 -\| | 按位或 -^ | 按位异或 -\~ | 按位取反 -\>\> | 右移 -<< | 左移 +符号|说明|特性 +|----|----|---| +& | 按位与 | 只要有0,返回0 +\| | 按位或| 只要有1,返回1 +^ | 按位异或| 相同返回0。不同返回1 +\~ | 按位取反| 全部反转 +\>\> | 右移| +<< | 左移| ## 2 特殊性质 操作 | 性质 |-----| -----| -n & (n - 1) | n中的最后一个1变成0 -^ | 相同的数抑或运算等于零。不同的数抑或运算等于1 +n & (n - 1) | n中的最后一个1变成0。 +n & (\~n + 1) | lowbit()运算,n中最后一个1保留。 n/2 | 等价于 右移一位 n >> 1 n*2 | 等价于 左移一位 n << 1 n % 2 |等价于 判断二进制最右一位值 n \& 1 diff --git a/算法/A类:基本算法/4.7 数组中的逆序对.md b/算法/A类:基本算法/4.7 数组中的逆序对.md new file mode 100644 index 00000000..3ee69d38 --- /dev/null +++ b/算法/A类:基本算法/4.7 数组中的逆序对.md @@ -0,0 +1,86 @@ +## 1 数组中的逆序对 + +### 问题描述 + +在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。 + +### 问题分析 + + + +### 策略选择 + +* 数据结构:线性数组 +* 算法思想:变质法。将搜索查找问题修改为排序问题。归并排序 + +### 算法设计 + +* 归并排序」是分治思想的典型应用,它包含这样三个步骤: + * 分解: 待排序的区间为 [l, r][l,r],令 m = \lfloor \frac{l + r}{2} \rfloorm=⌊2l+r⌋,我们把 [l, r][l,r] 分成 [l, m][l,m] 和 [m + 1, r][m+1,r] + * 解决: 使用归并排序递归地排序两个子序列 + * 合并: 把两个已经排好序的子序列 [l, m][l,m] 和 [m + 1, r][m+1,r] 合并起来 + +* 在归并排序过程中。后边列表的数添加到前边之后。前边列表中数的量,就是本次排序逆序数的量。 + +### 算法分析 + +* 时间复杂度:同归并排序 O(n \log n)O(nlogn)。 +* 空间复杂度:同归并排序 O(n)O(n), + +### 算法实现 + +```C++ +#include +#include +#include + +using namespace std; +class Solution { +public: + int reversePairs(vector& nums) { + + int count=0; + vector temps(nums.size()); + merge_sort(nums,0,nums.size()-1,count,temps); + return count; + + } + int merge_sort(vector&nums,int beg,int end,int &count,vector&temps){ + //进行头递归,分解 + int mid=0; + if(beg& nums) {