diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..b39548b9 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,30 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "name": "g++.exe - 生成和调试活动文件", + "type": "cppdbg", + "request": "launch", + "program": "${fileDirname}\\${fileBasenameNoExtension}.exe", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "miDebuggerPath": "D:\\mingw\\mingw64\\bin\\gdb.exe", + "setupCommands": [ + { + "description": "为 gdb 启用整齐打印", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ], + "preLaunchTask": "C/C++: g++.exe 生成活动文件" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..838cddca --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,46 @@ +{ + "tasks": [ + { + "type": "cppbuild", + "label": "C/C++: g++.exe 生成活动文件", + "command": "D:\\mingw\\mingw64\\bin\\g++.exe", + "args": [ + "-g", + "${file}", + "-o", + "${fileDirname}\\${fileBasenameNoExtension}.exe" + ], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": "build", + "detail": "调试器生成的任务。" + }, + { + "type": "cppbuild", + "label": "C/C++: g++.exe 生成活动文件 ver(1)", + "command": "D:\\mingw\\mingw64\\bin\\g++.exe", + "args": [ + "-g", + "${file}", + "-o", + "${fileDirname}\\${fileBasenameNoExtension}.exe" + ], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "调试器生成的任务。" + } + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/C++/标准库/2.1 顺序容器.md b/C++/标准库/2.1 顺序容器.md index 97f1ab9c..d7019151 100644 --- a/C++/标准库/2.1 顺序容器.md +++ b/C++/标准库/2.1 顺序容器.md @@ -31,7 +31,7 @@ ![](2021-03-05-20-42-30.png) > 操作记忆 -> * back、front、push_back、push_front、pop_back、pop_front。是一组首尾相关的操作。 +> * back、front、push_back、push_front、pop_back、pop_front、emplace_front、emplace_back。是一组首尾相关的插入操作。 > * insert、at、erase。是一组随机的操作。 @@ -48,6 +48,12 @@ > 与数组完全一致,只是定义方式不同。 > 数组不能copy赋值,但是array可以copy赋值。 +### 定义 + +``` +array arr = {1, 2, 3, 4, 5}; +``` + ## 2 vector ## 3 deque diff --git a/C++/标准库/4 算法.md b/C++/标准库/4 算法.md index 21d0a36d..ac9d45c8 100644 --- a/C++/标准库/4 算法.md +++ b/C++/标准库/4 算法.md @@ -29,24 +29,38 @@ | 3 | 排序算法Sorting/Partitions/Binary search/(对序列排序、合并、搜索算法操作。) | | 4 | 数值算法Merge/Heap/Min/max(对容器内容进行数值计算。) | -## 1.1 基础算法 -### 填充 +### 参数说明 + +|参数|说明| +|----|----| +beg|开始迭代器 +end|终止迭代器 +val|值 +func|操作函数 +n|整数 +comp|比较函数,返回true/false +binary|判断函数,返回true/false + +## 1.1 基础算法(遍历算法) + + +### 检查 +* 检查谓词是否对范围中所有、任一或无元素为 true | 函数 | 作用 | |---|---| -| fill(beg,end,val) | 将值val赋给[beg,end)范围内的所有元素。 | -| fill_n(beg,n,val) | 将值val赋给[beg,beg+n)范围内的所有元素。 | -| generate(beg,end,func) | 连续调用函数func填充[beg,end)范围内的所有元素。 | -| generate_n(beg,n,func) | 连续调用函数func填充[beg,beg+n)范围内的所有元素。 | +|all_of(beg,end,binary)|检查所有的元素是否满足binary| +|any_of(beg,end,binary)|检查任意的元素是否满足binary| +|none_of(beg,end,binary)|检查没有元素满足binary| -### 遍历、变换 +### 遍历变换 | 函数 | 作用 | |---|---| | for_each(beg,end,func) | 将[beg,end)范围内所有元素依次调用函数func,返回func。不修改序列中的元素。 | | transform(beg,end,res,unary) | 将[beg,end)范围内所有元素依次调用函数unary,结果放入res中。 | -| transform(beg2,end1,beg2,res,binary) | 将[beg,end)范围内所有元素与[beg2,beg2+end-beg)中所有元素依次调用函数unary,结果放入res中。 | +| transform(beg1,end1,beg2,res,binary) | 将[beg,end)范围内所有元素与[beg2,beg2+end-beg)中所有元素依次调用函数unary,结果放入res中。 | ### 最大最小 | 函数 | 作用 | @@ -59,6 +73,10 @@ | min(a,b,cmp) | 使用自定义比较操作cmp,返回两个元素中较小一个。 | | min_element(beg,end) | 返回一个ForwardIterator,指出[beg,end)中最小的元素。 | | min_element(beg,end,cmp) | 使用自定义比较操作cmp,返回一个ForwardIterator,指出[beg,end)中最小的元素。 | +|minmax(beg,end)|返回一个pair,包含最小最大值| +|minmax(beg,end,cmp)|自定义比较函数| +|minmax_element(beg,end)|返回一个pair,包含最大最小值的位置| +|minmax_element(beg,end,cmp)|自定义比较函数| ## 1.2 排序算法(12个) @@ -106,7 +124,7 @@ | count(beg,end,val) | 利用==操作符,对[beg,end)的元素与val进行比较,返回相等元素个数。 | | count_if(beg,end,pred) | 使用函数pred代替==操作符执行count()。 | -### 查找 +### 查找(查找某个值) | 函数 | 作用 | |---|---| @@ -119,7 +137,7 @@ | adjacent_find(beg,end) | 对[beg,end)的元素,查找一对相邻重复元素,找到则返回指向这对元素的第一个元素的ForwardIterator。否则返回end。 | | adjacent_find(beg,end,pred) | 使用函数pred代替==操作符执行adjacent_find()。 | -### 搜索 +### 搜索(搜索某个序列) | 函数 | 作用 | |---|---| @@ -141,7 +159,17 @@ | equal_range(beg,end,val) | 返回一对iterator,第一个表示lower_bound,第二个表示upper_bound。 | | equal_range(beg,end,val,comp) | 使用函数comp代替比较操作符执行lower_bound()。 | -## 1.4 删除和替换算法15个 +## 1.4 填充复制移除替换算法19个 + +### 填充 + + +| 函数 | 作用 | +|---|---| +| fill(beg,end,val) | 将值val赋给[beg,end)范围内的所有元素。 | +| fill_n(beg,n,val) | 将值val赋给[beg,beg+n)范围内的所有元素。 | +| generate(beg,end,func) | 连续调用函数func填充[beg,end)范围内的所有元素。 | +| generate_n(beg,n,func) | 连续调用函数func填充[beg,beg+n)范围内的所有元素。 | ### 复制 @@ -185,7 +213,9 @@ | swap_range(beg1,end1,beg2) | 将[beg1,end1)内的元素[beg2,beg2+beg1-end1)元素值进行交换。 | | iter_swap(it_a,it_b) | 交换两个ForwardIterator的值。 | -## 1.5 算数算法(4个) +## 1.5 算术算法(4个) + +`#inlcude` | 函数 | 作用 | |---|---| @@ -239,12 +269,14 @@ | 函数 | 作用 | |---|---| +|is_heap(beg,end[,comp]) | 检查给定范围是否为一个最大堆| +|is_heap_until(beg,end[,comp]) | 查找能成为最大堆的最大子范围| | make_heap(beg,end) | 把[beg,end)内的元素生成一个堆。 | | make_heap(beg,end,comp) | 将函数comp代替<操作符,执行make_heap()。 | -| pop_heap(beg,end) | 重新排序堆。它把first和last-1交换,然后重新生成一个堆。可使用容器的back来访问被"弹出"的元素或者使用pop_back进行真正的删除。并不真正把最大元素从堆中弹出。 | -| pop_heap(beg,end,comp) | 将函数comp代替<操作符,执行pop_heap()。 | | push_heap(beg,end) | 假设first到last-1是一个有效堆,要被加入到堆的元素存放在位置last-1,重新生成堆。在指向该函数前,必须先把元素插入容器后。 | | push_heap(beg,end,comp) | 将函数comp代替<操作符,执行push_heap()。 | +| pop_heap(beg,end) | 重新排序堆。它把first和last-1交换,然后重新生成一个堆。可使用容器的back来访问被"弹出"的元素或者使用pop_back进行真正的删除。并不真正把最大元素从堆中弹出。 | +| pop_heap(beg,end,comp) | 将函数comp代替<操作符,执行pop_heap()。 | | sort_heap(beg,end) | 对[beg,end)内的序列重新排序。 | | sort_heap(beg,end,comp) | 将函数comp代替<操作符,执行push_heap()。 | diff --git a/C++/面试/17.临时对象.md b/C++/面试/17.临时对象.md new file mode 100644 index 00000000..8818aaf4 --- /dev/null +++ b/C++/面试/17.临时对象.md @@ -0,0 +1,20 @@ +# 临时对象 + +### 定义 +* C++直接调用构造函数。会创建临时对象。 +* 临时对象没有对象名,它的生命周期只有一条语句。 +* 如果直接调用构造函数,赋值给一个具体的名字,则会使用复制初始化。则不是临时对象。 + +### 使用 + +* 如果做为函数参数使用。调用的是复制噶偶早函数。 +``` +vector > vec; +vec.push_back(vector(5));//直接调用构造函数,会创建临时对象。 +``` +* 如果作为返回值使用。调用的是复制构造函数。 +``` +vector hello(){ + return vector(); +} +``` diff --git a/C++/面试/6.高低地址与高低位.md b/C++/面试/6.高低地址与高低位.md index c58f5738..8a9886ba 100644 --- a/C++/面试/6.高低地址与高低位.md +++ b/C++/面试/6.高低地址与高低位.md @@ -3,7 +3,7 @@ ### 基础 -可以把主存看成一本空白bai的作业本,你现在要在笔记本上记录一些内容,他的页码排序是 +可以把主存看成一本空白的作业本,你现在要在笔记本上记录一些内容,他的页码排序是 ``` 第一页 : 0x0000001 @@ -13,12 +13,12 @@ ``` -1. 如果你选择`从前向后记录`(用完第一页,用第二页,类推)这就是先使用低地址,后使用高地址.业内表述:动态分配内存时堆空间向高地址增长,说的就是这种情况.这个向高地址增长就是先使用低地址,后使用高地址的意思. +1. 如果你选择`从前向后记录`(用完第一页,用第二页,类推)这就是先使用低地址,后使用高地址.业内表述:动态分配内存时**堆空间向高地址增长**,说的就是这种情况.这个向高地址增长就是先使用低地址,后使用高地址的意思. ``` 0x0000001 -> 0x0000002-> ... -> 0x0000092 ``` -1. 如果你选择`从后往前记录`(先用笔记本的最后一页,用完后使用倒数第二页,类推) 这就是先使用高地址,后使用低地址.业内表述:`0xbfac 5000-0xbfad a000`是栈空间,其中高地址的部分保存着进程的环境变量和命令行参数,低地址的部分保存函数栈帧,**栈空间是向低地址增长的**.这个向低地址增长就是先使用高地址,后使用低地址的意思. +2. 如果你选择`从后往前记录`(先用笔记本的最后一页,用完后使用倒数第二页,类推) 这就是先使用高地址,后使用低地址.业内表述:`0xbfac 5000-0xbfad a000`是栈空间,其中高地址的部分保存着进程的环境变量和命令行参数,低地址的部分保存函数栈帧,**栈空间是向低地址增长的**.这个向低地址增长就是先使用高地址,后使用低地址的意思. ``` 0x0000092 -> ... ->0x0000002 -> 0x0000001 ``` diff --git a/JavaScript/教程/程序控制结构和函数.md b/JavaScript/教程/程序控制结构和函数.md index 63bb274f..15f7ca9a 100644 --- a/JavaScript/教程/程序控制结构和函数.md +++ b/JavaScript/教程/程序控制结构和函数.md @@ -1,13 +1,12 @@ -\>程序控制结构简写 - -\>\>if-else条件判断选择 +## 程序控制结构简写 +### if-else条件判断选择 +``` if(判断条件){分支1} - else {分支2} - -\>\>switch多项选择结构,注意关键字default、break; - +``` +### switch多项选择结构,注意关键字default、break; +``` switch(选择变量){ case 1:分支1;break; @@ -19,43 +18,58 @@ case 3:分支3break; default:分支4 } +``` -\>\>for(初始化;循环条件;循环控制){ +### for循环 + +``` +for(初始化;循环条件;循环控制){ 循环体; } +``` -\>\>while(判断语句){循环体} +### while循环 -\>\>关键字continue(退出本次循环开始新的循环) +``` +while(判断语句){循环体} +``` +* 关键字continue(退出本次循环开始新的循环) -\>\>关键字break(退出整个循环) +* 关键字break(退出整个循环) -\>JS中的函数 - -\>\>函数的定义: +## 2 JS中的函数 +### 函数的定义: +``` function fun(){函数体} - -\>\>函数调用 - +``` +### 函数调用 +``` 函数名(实际参数); - -\>\>参数传递 - +``` +### 参数传递 +``` 可以传递任意数量的参数, 不用声明参数类型 - -\>\>函数返回值 - +``` +### 函数返回值 +``` 可以返回任意类型的返回值。 +``` +### 函数声明 +``` +是函数在任何地方都可以定义 +``` +### 函数作用域 +``` +全局函数,对象的函数 +``` +### this +* 函数体中this说明当前函数方法的作用域。可以是全局作用域,可以是整个windows对象,可能用于指向函数外部的变量。 -\>\>函数声明,是函数在任何地方都可以定义 +### call +* call(this的指向, 变量):可以改变当前函数中this的指向 -\>\>函数作用域,全局函数,对象的函数 - -\>\>this:函数体中this说明当前函数方法的作用域。可以是全局作用域,可以是整个windows对象,可能用于指向函数外部的变量。 - -\>\>call(this的指向, 变量):可以改变当前函数中this的指向 - -\>\>apply(this的指向,数组):改变当前函数中this的指向 +### apply +* apply(this的指向,数组):改变当前函数中this的指向 diff --git a/工作日志/2021年2月27日-三月份计划.md b/工作日志/2021年2月27日-三月份计划.md index ff9be338..d67d754c 100644 --- a/工作日志/2021年2月27日-三月份计划.md +++ b/工作日志/2021年2月27日-三月份计划.md @@ -7,9 +7,9 @@ ### 工作 -- 制作简历(明天) -- 知识复习——语言(一周) - - C++(primer) +- 制作简历(明天)√ +- 知识复习——语言 + - C++(primer)(两周) - 基础语法√ - 标准库 STL√ - 面向对象√ @@ -17,12 +17,43 @@ - effective 系列 - 系列视频 - 问题专项解决) - - Java(语法、标准库) -- 知识复习——算法(一周) - - 数据结构 - - 算法 + - Java(两周) + - 语法 + - 标准库(网络编程、多线程、IO) + - Spring框架 +- 知识复习——算法与数据结构(一周) + - 数据结构《大话数据结构》 + - 算法《数据结构与算法分析》《算法图解》 - 知识复习——基础(一周) - 计算机网络 - 数据库 - 操作系统 - Linux 与网络编程 +* 刷题 + * 力扣(学习、题库、讨论。侧重于刷算法类型的题目和相关讨论) + * 学习:3-5本书的学习需要阅读的书籍如下 + * 数组、链表、队列、树(堆)、图(数据结构专项) + * 初级算法 + * 中级算法 + * 高级算法 + * 题库:然后按照热度刷题库 + * 剑指offer + * 讨论:没事看讨论,主要是一些面经和基础知识,可以补充 + * 牛客网(学习、社区、求职基础知识的学习) + * 题库(知识用来刷题) + * 经典必刷题目 + * C++/JAVA专项练习 + + +### 问题(待处理) + +* 关于递归的方式。头递归。尾递归。递归前和递归后的处理。递归前的处理,影响或许递归。递归后的处理,恢复之前的影响。那个应该是一个典型的递归搜索路径的问题。 +* 关于树的处理。前序遍历中序遍历后续遍历。 +* 关于位运算的特殊总结。 + + +### 时间安排 + +* 知识复习——数据结构与算法 + * 明天早上,根据已经下载好的笔记,读博客已经收藏的博客,修改笔记。关键部分写代码。 +* 刷题——剑指offer、经典必刷提、C++专项练习。 \ No newline at end of file diff --git a/数据结构/0 数据结构基础.md b/数据结构/0 数据结构基础.md new file mode 100644 index 00000000..63d346c9 --- /dev/null +++ b/数据结构/0 数据结构基础.md @@ -0,0 +1,78 @@ +## 数据结构基础 + +> 参考文献 +> * [易佰教程](https://www.yiibai.com/data_structure) + +## 1 简介 +### 数据结构预算法 + +![](数据结构与算法.jpeg) + +### 基本术语 + +数据结构是任何程序或软件的构建块(基础块)。为程序选择适当的数据结构对于程序员来说是最困难的任务。就数据结构而言,使用以下术语 - +* 数据:数据可以定义为基本值或值集合,例如,学生的姓名和ID,成绩等就是学生的数据。 +* 组项:具有从属数据项的数据项称为组项,例如,学生的姓名由名字和姓氏组成。 +* 记录:记录可以定义为各种数据项的集合,例如,如果以学生实体为例,那么学生的名称,地址,课程和标记可以组合在一起形成学生的记录。 +* 文件:文件是一种类型实体的各种记录的集合,例如,如果类中有60名员工,则相关文件中将有20条记录,其中每条记录包含有关每个员工的数据。 +* 属性和实体:实体表示某些对象的类。它包含各种属性。每个属性表示该实体的特定属性。 +* 字段:字段是表示实体属性的单个基本信息单元。 + +### 为什么需要数据结构 + +随着应用程序变得越来越复杂,数据量日益增加,可能会出现以下问题: + +* 处理器速度:要处理非常大的数据,需要高速处理,但随着数据逐日增长到每个实体数十亿个文件,处理器可能无法处理大量数据。 +* 数据搜索:假设商店的库存大小是100860个商品,如果应用程序需要搜索某一特定商品,则每次需要遍历100860个商品,这会导致搜索过程变慢。 +* 大量请求:如果成千上万的用户在Web服务器上同时搜索数据,在此过程中可能在短时会有一个非常大请求而导致服务器处理不了。 + +### 数据结构的优点 + +* 效率:程序的效率取决于数据结构的选择。 +* 可重用性:数据结构是可重用的,即当实现了特定的数据结构,就可以在其他地方使用它。也将数据结构的实现编译到不同客户端使用的程序库中 +* 抽象:数据结构由ADT指定,它提供抽象级别。 客户端程序仅通过接口使用数据结构,而不涉及实现细节。 + + +## 2 数据结构分类 + + +![](2021-03-12-17-07-01.png) + +### 线性数据结构 + +如果数据结构的所有元素按线性顺序排列,则称为线性数据结构。 在线性数据结构中,元素以非分层方式存储,除了第一个和最后一个元素,它的每个元素具有后继元素和前导元素。 +线性数据结构的类型如下: + +* 数组:数组是类似数据项的集合,每个数据项称为数组的元素。 元素的数据类型可以是任何有效的数据类型,如char,int,float或double。数组的元素共享相同的变量名,但每个元素都带有一个不同的索引号,这些索引号也称为下标。 数组可以是一维的,二维的或多维的。 +``` +age[0], age[1], age[2], age[3],.... age[98], age[99] +``` + +* 链表:链表是一种线性数据结构,用于维护内存中的列表。 它可以看作存储在非连续内存位置的节点集合。链表中的每个节点都包含指向其相邻节点的指针。 +* 堆栈 :堆栈是一个线性列表,其中只允许在一端插入和删除,称为顶部。堆栈是一种抽象数据类型(ADT),可以在大多数编程语言中实现。 它被命名为堆栈,因为它的行为类似于真实世界的堆栈,例如:成堆的板块或卡片组等,只能在最顶面上操作。 +* 队列:队列是一个线性列表,它的元素只能在一端插入(添加),也被称为后端,而只在另一端出队(删除),也被称为前端。 + +### 非线性数据结构 + +非线性数据结构不形成序列,即每个项目或元素以非线性排列与两个或更多个其他项目连接。 数据元素不按顺序结构排列。 +非线性数据结构的类型如下: + +* 树:树是多级数据结构,其元素之间具有层次关系,树的元素也称为节点。层次中最底层的节点称为叶节点,而最顶层节点称为根节点。 每个节点都包含指向相邻节点的指针。树数据结构基于节点之间的父子关系。 除了叶节点之外,树中的每个节点可以具有多个子节点,而除了根节点之外,每个节点可以具有最多一个父节点。 树可以分为许多类别,本教程在稍后章节中将对此进行讨论。 +* 图:图可以定义为由称为边缘的链接连接的元素集(由顶点表示)的图表示。 图不同于树,图可以有循环而树不能具有循环。 + + +## 3 数据结构的操作 + +> 从数据结构的角度 + +* 遍历:每个数据结构都包含一组数据元素。遍历数据结构表示访问数据结构的每个元素,以便执行某些特定操作,如搜索或排序。示例 :如果需要计算学生在6个不同科目中获得的分数的平均值,需要遍历完整的分数数组并计算总和,然后将总分数除以科目数,即6, 最后得到平均值。 +* 插入:插入是在任何位置将元素添加到数据结构的过程。如果数据结构的大小是n,那么只能在n-1个数据元素之间插入元素。 +* 删除:从数据结构中删除元素的过程称为删除。 可以在任何随机位置删除数据结构中的元素。如果要从空数据结构中删除元素,则会发生下溢。 +* 搜索:在数据结构中查找元素位置的过程称为搜索。 有两种算法可以执行搜索,即线性搜索和二进制搜索。在本教程后面讨论这两种搜索算法。 +* 排序:按特定顺序排列数据结构的过程称为排序。 有许多算法可用于执行排序,例如,插入排序,选择排序,冒泡排序等。 +* 更新:为更新数据结构中的现有元素而开发的算法。 +* 合并:当两个列表分别为大小为M和N的列表A和列表B时,相似类型的元素,连接产生第三个列表,列表C的大小(M + N),则此过程称为合并。 + + + + diff --git a/数据结构/1 数组.md b/数据结构/1 数组.md new file mode 100644 index 00000000..ed73e66b --- /dev/null +++ b/数据结构/1 数组.md @@ -0,0 +1,169 @@ +# 数组 + + +## 1 数组的简介 + +### 定义 +* 数组是存储在**连续内存位置**的**相同类型数据项**的集合,是最简单的**线性数据结构**。 + * 数组的每个数据元素都可以使用下表索引运算符,进行**随机访问**。 + * 数组可以有一个或**多个维度**。 + * 每个元素具有**相同的数据类型**并且具有相同的大小,即int = 4个字节。 + +``` +int arr[10]; +char arr[10]; +float arr[5]; +``` + + +### 优点 +* 数组为同一类型的变量组提供单一名称,因此很容易记住数组中所有元素的名称。 +* 遍历数组是一个非常简单的过程,只需要递增数组的基址,就可以逐个访问每个元素。 +* 可以使用索引直接访问数组中的任何元素。 + + + +### 时间复杂性 + +| 算法 | 平均情况 | 最坏情况 | +|----|------|------| +| 访问 | O(1) | O(1) | +| 搜索 | O(n) | O(n) | +| 插入 | O(n) | O(n) | +| 删除 | O(n) | O(n) | + +### 空间复杂性 + +* 在数组中,最坏情况下的空间复杂度是O(n)。 + +## 2 数组的类型 +### 一维数组 + +* 一维(或单维)数组是一种线性数组,其中元素的访问是以行或列索引的单一下标表示。 +* C++ 将高维维数组存储为一维数组。因此,如果我们将 A 定义为也包含 M * N 个元素的二维数组,那么实际上 A[i][j] 就等于 A[i * N + j]。 + + + +### 多维数组 + +* 普通数组采用一个整数来作下标。多维数组(高维数组)的概念特别是在数值计算和图形应用方面非常有用。我们在多维数组之中采用一系列有序的整数来标注,如在[ 3,1,5 ] 。这种整数列表之中整数的个数始终相同,且被称为数组的“维度”。关于每个数组维度的边界称为“维”。维度为 k 的数组通常被称为 k 维。 +* 多维数组的数组名字,在表达式中自动转换为数组首元素地址值,但这个首元素实际上是去除数组下标第一维之后的数组剩余部分。 + +## 3 数组的存储和实现 +### 数组存储 + +* 数组的所有数据元素都存储在主存储器中的连续位置。 +* 数组名称表示主存储器中的基地址或第一个元素的地址。 +* 数组的每个元素都由适当的索引表示。可以用三种方式定义数组的索引。 + * 0(从零开始索引):数组的第一个元素是arr[0]。 + * 1(基于一个索引):数组的第一个元素是arr [1]。 + * n(基于n的索引):基于数组的第一个元素,可以定位任何随机索引值。 + +![](2021-03-12-20-47-17.png) + +### 数组实现 +* C++内置数组数据类型。 +``` +int a[4]; +double b[3][9];//内置数组 +``` +* STL提供两种灵活的数组结构。 + +``` +array arr = {1, 2, 3, 4, 5};//STL模板数组 +vector vec ={3,4,3};//STL模板向量,可变长度的数组 +``` + +## 4 数组的操作 + +### 基础操作 +* 创建 +* 遍历 +* 插入 +* 删除 + +### 创建 +``` +// 定义数组 +int size =20;//容量 +int numbers =10;//数据量 +int a[20]={1,3,4,5,6,7,4,2,4,5}; +``` +### 遍历 +```C++ +// 数组遍历 +for(int i=0;i<10;i++){ + cout<=numbers){ + cout<<"overbound"< +using namespace std; + +int main(){ + // 定义数组 + int size =20;//容量 + int numbers =10;//数据量 + int a[20]={1,3,4,5,6,7,4,2,4,5}; + + // 数组遍历 + for(int i=0;i<10;i++){ + cout<=numbers){ + cout<<"overbound"< + +list li; +forward_list li; +``` + +## 3 链表的操作 +### 基本操作 +* 创建 +* 遍历、搜索、查找(同一类操作) +* 插入(尾插入、头插入、中间插入) +* 删除 + +### 实现 +```C++ +#include +#include +struct node +{ + int data; + struct node *next; +}; +struct node *head; + +void beginsert(); +void lastinsert(); +void randominsert(); +void begin_delete(); +void last_delete(); +void random_delete(); +void display(); +void search(); + +int main() +{ + int choice = 0; + while (choice != 9) + { + printf("\n\n********* 主菜单 *********\n"); + printf("从以下菜单列表中选择一个选项操作 ...\n"); + printf("===============================================\n"); + printf("1.插入到开头\n"); + printf("2.插入到结尾\n"); + printf("3.插入任何随机位置\n"); + printf("4.从头部删除\n"); + printf("5.从尾部删除\n"); + printf("6.删除指定位置后的节点\n"); + printf("7.搜索元素\n"); + printf("8.显示链表中的数据\n"); + printf("9.退出\n\n"); + printf("===============================================\n"); + printf("请输入您的选择:"); + scanf("%d", &choice); + switch (choice) + { + case 1: + beginsert(); + break; + case 2: + lastinsert(); + break; + case 3: + randominsert(); + break; + case 4: + begin_delete(); + break; + case 5: + last_delete(); + break; + case 6: + random_delete(); + break; + case 7: + search(); + break; + case 8: + display(); + break; + case 9: + exit(0); + break; + default: + printf("请输入有效的选项..."); + } + } + return 0; +} +void beginsert() +{ + struct node *ptr; + int item; + ptr = (struct node *) malloc(sizeof(struct node *)); + if (ptr == NULL) + { + printf("内存不够!\n"); + } + else + { + printf("请输入一个整数值:"); + scanf("%d", &item); + ptr->data = item; + ptr->next = head; + head = ptr; + printf("节点已经成功插入\n"); + } + +} +void lastinsert() +{ + struct node *ptr, *temp; + int item; + ptr = (struct node*)malloc(sizeof(struct node)); + if (ptr == NULL) + { + printf("内存不够!\n"); + } + else + { + printf("请输入一个整数值:"); + scanf("%d", &item); + ptr->data = item; + if (head == NULL) + { + ptr->next = NULL; + head = ptr; + printf("节点已经成功插入\n"); + } + else + { + temp = head; + while (temp->next != NULL) + { + temp = temp->next; + } + temp->next = ptr; + ptr->next = NULL; + printf("节点已经成功插入\n"); + + } + } +} +void randominsert() +{ + int i, loc, item; + struct node *ptr, *temp; + ptr = (struct node *) malloc(sizeof(struct node)); + if (ptr == NULL) + { + printf("内存不够!\n"); + } + else + { + printf("请输入一个整数值:"); + scanf("%d", &item); + ptr->data = item; + printf("输入要插入的位置:"); + scanf("%d", &loc); + temp = head; + for (i = 0;i < loc;i++) + { + temp = temp->next; + if (temp == NULL) + { + printf("此处不能插入节点\n"); + return; + } + + } + ptr->next = temp->next; + temp->next = ptr; + printf("节点已经成功插入\n"); + } +} +void begin_delete() +{ + struct node *ptr; + if (head == NULL) + { + printf("链表为空,没有什么可以删除!\n"); + } + else + { + ptr = head; + head = ptr->next; + free(ptr); + printf("已经删除头部节点 ...\n"); + } +} +void last_delete() +{ + struct node *ptr, *ptr1; + if (head == NULL) + { + printf("链表为空,没有什么可以删除!\n"); + } + else if (head->next == NULL) + { + head = NULL; + free(head); + printf("唯一的节点已经被删除了...\n"); + } + + else + { + ptr = head; + while (ptr->next != NULL) + { + ptr1 = ptr; + ptr = ptr->next; + } + ptr1->next = NULL; + free(ptr); + printf("已删除最后一个节点...\n"); + } +} +void random_delete() +{ + struct node *ptr, *ptr1; + int loc, i; + printf("输入要在此节点之后执行删除的节点的位置:"); + scanf("%d", &loc); + ptr = head; + for (i = 0;i < loc;i++) + { + ptr1 = ptr; + ptr = ptr->next; + + if (ptr == NULL) + { + printf("不能删除\n"); + return; + } + } + ptr1->next = ptr->next; + free(ptr); + printf("\n第 %d 个节点已经被删除了", loc + 1); +} +void search() +{ + struct node *ptr; + int item, i = 0, flag; + ptr = head; + if (ptr == NULL) + { + printf("链表为空!\n"); + } + else + { + printf("请输入要搜索的项目:"); + scanf("%d", &item); + while (ptr != NULL) + { + if (ptr->data == item) + { + printf("在 %d 位置找到数据项\n", i + 1); + flag = 0; + } + else + { + flag = 1; + } + i++; + ptr = ptr->next; + } + if (flag == 1) + { + printf("数据项未找到\n"); + } + } + +} + +/** + * 显示链表中的数据 + */ +void display() +{ + struct node *ptr; + ptr = head; + if (ptr == NULL) + { + printf("链表为空,没有数据可以显示。"); + } + else + { + printf("链表中的数据值如下所示:\n"); + printf("--------------------------------------------------\n"); + while (ptr != NULL) + { + printf("\n%d", ptr->data); + ptr = ptr->next; + } + } + printf("\n\n\n"); + +} +``` \ No newline at end of file diff --git a/数据结构/2.1.cpp b/数据结构/2.1.cpp new file mode 100644 index 00000000..57f4df4c --- /dev/null +++ b/数据结构/2.1.cpp @@ -0,0 +1,282 @@ +#include +#include +struct node +{ + int data; + struct node *next; +}; +struct node *head; + +void beginsert(); +void lastinsert(); +void randominsert(); +void begin_delete(); +void last_delete(); +void random_delete(); +void display(); +void search(); + +int main() +{ + int choice = 0; + while (choice != 9) + { + printf("\n\n********* 主菜单 *********\n"); + printf("从以下菜单列表中选择一个选项操作 ...\n"); + printf("===============================================\n"); + printf("1.插入到开头\n"); + printf("2.插入到结尾\n"); + printf("3.插入任何随机位置\n"); + printf("4.从头部删除\n"); + printf("5.从尾部删除\n"); + printf("6.删除指定位置后的节点\n"); + printf("7.搜索元素\n"); + printf("8.显示链表中的数据\n"); + printf("9.退出\n\n"); + printf("===============================================\n"); + printf("请输入您的选择:"); + scanf("%d", &choice); + switch (choice) + { + case 1: + beginsert(); + break; + case 2: + lastinsert(); + break; + case 3: + randominsert(); + break; + case 4: + begin_delete(); + break; + case 5: + last_delete(); + break; + case 6: + random_delete(); + break; + case 7: + search(); + break; + case 8: + display(); + break; + case 9: + exit(0); + break; + default: + printf("请输入有效的选项..."); + } + } + return 0; +} +void beginsert() +{ + struct node *ptr; + int item; + ptr = (struct node *) malloc(sizeof(struct node *)); + if (ptr == NULL) + { + printf("内存不够!\n"); + } + else + { + printf("请输入一个整数值:"); + scanf("%d", &item); + ptr->data = item; + ptr->next = head; + head = ptr; + printf("节点已经成功插入\n"); + } + +} +void lastinsert() +{ + struct node *ptr, *temp; + int item; + ptr = (struct node*)malloc(sizeof(struct node)); + if (ptr == NULL) + { + printf("内存不够!\n"); + } + else + { + printf("请输入一个整数值:"); + scanf("%d", &item); + ptr->data = item; + if (head == NULL) + { + ptr->next = NULL; + head = ptr; + printf("节点已经成功插入\n"); + } + else + { + temp = head; + while (temp->next != NULL) + { + temp = temp->next; + } + temp->next = ptr; + ptr->next = NULL; + printf("节点已经成功插入\n"); + + } + } +} +void randominsert() +{ + int i, loc, item; + struct node *ptr, *temp; + ptr = (struct node *) malloc(sizeof(struct node)); + if (ptr == NULL) + { + printf("内存不够!\n"); + } + else + { + printf("请输入一个整数值:"); + scanf("%d", &item); + ptr->data = item; + printf("输入要插入的位置:"); + scanf("%d", &loc); + temp = head; + for (i = 0;i < loc;i++) + { + temp = temp->next; + if (temp == NULL) + { + printf("此处不能插入节点\n"); + return; + } + + } + ptr->next = temp->next; + temp->next = ptr; + printf("节点已经成功插入\n"); + } +} +void begin_delete() +{ + struct node *ptr; + if (head == NULL) + { + printf("链表为空,没有什么可以删除!\n"); + } + else + { + ptr = head; + head = ptr->next; + free(ptr); + printf("已经删除头部节点 ...\n"); + } +} +void last_delete() +{ + struct node *ptr, *ptr1; + if (head == NULL) + { + printf("链表为空,没有什么可以删除!\n"); + } + else if (head->next == NULL) + { + head = NULL; + free(head); + printf("唯一的节点已经被删除了...\n"); + } + + else + { + ptr = head; + while (ptr->next != NULL) + { + ptr1 = ptr; + ptr = ptr->next; + } + ptr1->next = NULL; + free(ptr); + printf("已删除最后一个节点...\n"); + } +} +void random_delete() +{ + struct node *ptr, *ptr1; + int loc, i; + printf("输入要在此节点之后执行删除的节点的位置:"); + scanf("%d", &loc); + ptr = head; + for (i = 0;i < loc;i++) + { + ptr1 = ptr; + ptr = ptr->next; + + if (ptr == NULL) + { + printf("不能删除\n"); + return; + } + } + ptr1->next = ptr->next; + free(ptr); + printf("\n第 %d 个节点已经被删除了", loc + 1); +} +void search() +{ + struct node *ptr; + int item, i = 0, flag; + ptr = head; + if (ptr == NULL) + { + printf("链表为空!\n"); + } + else + { + printf("请输入要搜索的项目:"); + scanf("%d", &item); + while (ptr != NULL) + { + if (ptr->data == item) + { + printf("在 %d 位置找到数据项\n", i + 1); + flag = 0; + } + else + { + flag = 1; + } + i++; + ptr = ptr->next; + } + if (flag == 1) + { + printf("数据项未找到\n"); + } + } + +} + +/** + * 显示链表中的数据 + */ +void display() +{ + struct node *ptr; + ptr = head; + if (ptr == NULL) + { + printf("链表为空,没有数据可以显示。"); + } + else + { + printf("链表中的数据值如下所示:\n"); + printf("--------------------------------------------------\n"); + while (ptr != NULL) + { + printf("\n%d", ptr->data); + ptr = ptr->next; + } + } + printf("\n\n\n"); + +} \ No newline at end of file diff --git a/数据结构/2.2 双链表.md b/数据结构/2.2 双链表.md new file mode 100644 index 00000000..e9766ef7 --- /dev/null +++ b/数据结构/2.2 双链表.md @@ -0,0 +1,329 @@ +# 双链表 + +## 1 双链表的简介 +### 概念 + +* 双向链表是一种复杂类型的链表,它的节点包含指向序列中前一个节点和下一个节点的指针。 +* 在双向链表中,节点由三部分组成:节点数据,指向下一个节点的指针(next指针),指向前一个节点的指针(prev指针)。 + +![](2021-03-12-21-14-57.png) +![](2021-03-12-21-14-50.png) + +## 2 双链表的存储和实现 + +### 数据存储 +* 双向链表为每个节点消耗更多空间 +* 可以更灵活地操作链表中的元素。 + +![](双链表的数据存储.png) + +### 数据实现 +* 使用结构体和指针实现。 +```C +struct node +{ + struct node *prev; + int data; + struct node *next; +} +``` +* 使用STL中的list实现 + +``` +#include +list li; +``` +## 3 双链表的操作 +### 基本操作 +* 创建 +* 遍历、搜索 +* 插入 +* 删除 + + +### 实现 +```C++ +#include +#include +struct node +{ + struct node *prev; + struct node *next; + int data; +}; +struct node *head; +void insertion_beginning(); +void insertion_last(); +void insertion_specified(); +void deletion_beginning(); +void deletion_last(); +void deletion_specified(); +void display(); +void search(); +void main() +{ + int choice = 0; + while (choice != 9) + { + printf("*********Main Menu*********\n"); + printf("Choose one option from the following list ...\n"); + printf("===============================================\n"); + printf("1.Insert in begining\n2.Insert at last\n3.Insert at any random location\n4.Delete from Beginning\n5.Delete from last\n6.Delete the node after the given data\n7.Search\n8.Show\n9.Exit\n"); + printf("Enter your choice?\n"); + scanf("%d", &choice); + switch (choice) + { + case 1: + insertion_beginning(); + break; + case 2: + insertion_last(); + break; + case 3: + insertion_specified(); + break; + case 4: + deletion_beginning(); + break; + case 5: + deletion_last(); + break; + case 6: + deletion_specified(); + break; + case 7: + search(); + break; + case 8: + display(); + break; + case 9: + exit(0); + break; + default: + printf("Please enter valid choice.."); + } + } +} +void insertion_beginning() +{ + struct node *ptr; + int item; + ptr = (struct node *)malloc(sizeof(struct node)); + if (ptr == NULL) + { + printf("OVERFLOW\n"); + } + else + { + printf("Enter Item value"); + scanf("%d", &item); + + if (head == NULL) + { + ptr->next = NULL; + ptr->prev = NULL; + ptr->data = item; + head = ptr; + } + else + { + ptr->data = item; + ptr->prev = NULL; + ptr->next = head; + head->prev = ptr; + head = ptr; + } + printf("Node inserted\n"); + } + +} +void insertion_last() +{ + struct node *ptr, *temp; + int item; + ptr = (struct node *) malloc(sizeof(struct node)); + if (ptr == NULL) + { + printf("OVERFLOW\n"); + } + else + { + printf("Enter value"); + scanf("%d", &item); + ptr->data = item; + if (head == NULL) + { + ptr->next = NULL; + ptr->prev = NULL; + head = ptr; + } + else + { + temp = head; + while (temp->next != NULL) + { + temp = temp->next; + } + temp->next = ptr; + ptr->prev = temp; + ptr->next = NULL; + } + + } + printf("node inserted\n"); +} +void insertion_specified() +{ + struct node *ptr, *temp; + int item, loc, i; + ptr = (struct node *)malloc(sizeof(struct node)); + if (ptr == NULL) + { + printf("OVERFLOW\n"); + } + else + { + temp = head; + printf("Enter the location "); + scanf("%d", &loc); + for (i = 0;i < loc;i++) + { + temp = temp->next; + if (temp == NULL) + { + printf("There are less than %d elements", loc); + return; + } + } + printf("Enter value"); + scanf("%d", &item); + ptr->data = item; + ptr->next = temp->next; + ptr->prev = temp; + temp->next = ptr; + temp->next->prev = ptr; + printf("node inserted\n"); + } +} +void deletion_beginning() +{ + struct node *ptr; + if (head == NULL) + { + printf("UNDERFLOW\n"); + } + else if (head->next == NULL) + { + head = NULL; + free(head); + printf("node deleted\n"); + } + else + { + ptr = head; + head = head->next; + head->prev = NULL; + free(ptr); + printf("node deleted\n"); + } + +} +void deletion_last() +{ + struct node *ptr; + if (head == NULL) + { + printf("UNDERFLOW\n"); + } + else if (head->next == NULL) + { + head = NULL; + free(head); + printf("node deleted\n"); + } + else + { + ptr = head; + if (ptr->next != NULL) + { + ptr = ptr->next; + } + ptr->prev->next = NULL; + free(ptr); + printf("node deleted\n"); + } +} +void deletion_specified() +{ + struct node *ptr, *temp; + int val; + printf("Enter the data after which the node is to be deleted : "); + scanf("%d", &val); + ptr = head; + while (ptr->data != val) + ptr = ptr->next; + if (ptr->next == NULL) + { + printf("Can't delete\n"); + } + else if (ptr->next->next == NULL) + { + ptr->next = NULL; + } + else + { + temp = ptr->next; + ptr->next = temp->next; + temp->next->prev = ptr; + free(temp); + printf("node deleted\n"); + } +} +void display() +{ + struct node *ptr; + printf("printing values...\n"); + ptr = head; + while (ptr != NULL) + { + printf("%d\n", ptr->data); + ptr = ptr->next; + } +} +void search() +{ + struct node *ptr; + int item, i = 0, flag; + ptr = head; + if (ptr == NULL) + { + printf("Empty List\n"); + } + else + { + printf("Enter item which you want to search?\n"); + scanf("%d", &item); + while (ptr != NULL) + { + if (ptr->data == item) + { + printf("item found at location %d ", i + 1); + flag = 0; + break; + } + else + { + flag = 1; + } + i++; + ptr = ptr->next; + } + if (flag == 1) + { + printf("Item not found\n"); + } + } + +} +``` diff --git a/数据结构/2.2.cpp b/数据结构/2.2.cpp new file mode 100644 index 00000000..3ca45fd0 --- /dev/null +++ b/数据结构/2.2.cpp @@ -0,0 +1,283 @@ +#include +#include +struct node +{ + struct node *prev; + struct node *next; + int data; +}; +struct node *head; +void insertion_beginning(); +void insertion_last(); +void insertion_specified(); +void deletion_beginning(); +void deletion_last(); +void deletion_specified(); +void display(); +void search(); +void main() +{ + int choice = 0; + while (choice != 9) + { + printf("*********Main Menu*********\n"); + printf("Choose one option from the following list ...\n"); + printf("===============================================\n"); + printf("1.Insert in begining\n2.Insert at last\n3.Insert at any random location\n4.Delete from Beginning\n5.Delete from last\n6.Delete the node after the given data\n7.Search\n8.Show\n9.Exit\n"); + printf("Enter your choice?\n"); + scanf("%d", &choice); + switch (choice) + { + case 1: + insertion_beginning(); + break; + case 2: + insertion_last(); + break; + case 3: + insertion_specified(); + break; + case 4: + deletion_beginning(); + break; + case 5: + deletion_last(); + break; + case 6: + deletion_specified(); + break; + case 7: + search(); + break; + case 8: + display(); + break; + case 9: + exit(0); + break; + default: + printf("Please enter valid choice.."); + } + } +} +void insertion_beginning() +{ + struct node *ptr; + int item; + ptr = (struct node *)malloc(sizeof(struct node)); + if (ptr == NULL) + { + printf("OVERFLOW\n"); + } + else + { + printf("Enter Item value"); + scanf("%d", &item); + + if (head == NULL) + { + ptr->next = NULL; + ptr->prev = NULL; + ptr->data = item; + head = ptr; + } + else + { + ptr->data = item; + ptr->prev = NULL; + ptr->next = head; + head->prev = ptr; + head = ptr; + } + printf("Node inserted\n"); + } + +} +void insertion_last() +{ + struct node *ptr, *temp; + int item; + ptr = (struct node *) malloc(sizeof(struct node)); + if (ptr == NULL) + { + printf("OVERFLOW\n"); + } + else + { + printf("Enter value"); + scanf("%d", &item); + ptr->data = item; + if (head == NULL) + { + ptr->next = NULL; + ptr->prev = NULL; + head = ptr; + } + else + { + temp = head; + while (temp->next != NULL) + { + temp = temp->next; + } + temp->next = ptr; + ptr->prev = temp; + ptr->next = NULL; + } + + } + printf("node inserted\n"); +} +void insertion_specified() +{ + struct node *ptr, *temp; + int item, loc, i; + ptr = (struct node *)malloc(sizeof(struct node)); + if (ptr == NULL) + { + printf("OVERFLOW\n"); + } + else + { + temp = head; + printf("Enter the location "); + scanf("%d", &loc); + for (i = 0;i < loc;i++) + { + temp = temp->next; + if (temp == NULL) + { + printf("There are less than %d elements", loc); + return; + } + } + printf("Enter value"); + scanf("%d", &item); + ptr->data = item; + ptr->next = temp->next; + ptr->prev = temp; + temp->next = ptr; + temp->next->prev = ptr; + printf("node inserted\n"); + } +} +void deletion_beginning() +{ + struct node *ptr; + if (head == NULL) + { + printf("UNDERFLOW\n"); + } + else if (head->next == NULL) + { + head = NULL; + free(head); + printf("node deleted\n"); + } + else + { + ptr = head; + head = head->next; + head->prev = NULL; + free(ptr); + printf("node deleted\n"); + } + +} +void deletion_last() +{ + struct node *ptr; + if (head == NULL) + { + printf("UNDERFLOW\n"); + } + else if (head->next == NULL) + { + head = NULL; + free(head); + printf("node deleted\n"); + } + else + { + ptr = head; + if (ptr->next != NULL) + { + ptr = ptr->next; + } + ptr->prev->next = NULL; + free(ptr); + printf("node deleted\n"); + } +} +void deletion_specified() +{ + struct node *ptr, *temp; + int val; + printf("Enter the data after which the node is to be deleted : "); + scanf("%d", &val); + ptr = head; + while (ptr->data != val) + ptr = ptr->next; + if (ptr->next == NULL) + { + printf("Can't delete\n"); + } + else if (ptr->next->next == NULL) + { + ptr->next = NULL; + } + else + { + temp = ptr->next; + ptr->next = temp->next; + temp->next->prev = ptr; + free(temp); + printf("node deleted\n"); + } +} +void display() +{ + struct node *ptr; + printf("printing values...\n"); + ptr = head; + while (ptr != NULL) + { + printf("%d\n", ptr->data); + ptr = ptr->next; + } +} +void search() +{ + struct node *ptr; + int item, i = 0, flag; + ptr = head; + if (ptr == NULL) + { + printf("Empty List\n"); + } + else + { + printf("Enter item which you want to search?\n"); + scanf("%d", &item); + while (ptr != NULL) + { + if (ptr->data == item) + { + printf("item found at location %d ", i + 1); + flag = 0; + break; + } + else + { + flag = 1; + } + i++; + ptr = ptr->next; + } + if (flag == 1) + { + printf("Item not found\n"); + } + } + +} \ No newline at end of file diff --git a/数据结构/2.3 循环单链表.md b/数据结构/2.3 循环单链表.md new file mode 100644 index 00000000..e9fd6ab7 --- /dev/null +++ b/数据结构/2.3 循环单链表.md @@ -0,0 +1,297 @@ + +# 循环单向链表 +## 1 简介 +### 概念 +* 在循环单链表中,链表的最后一个节点包含指向链表的第一个节点的指针。可以有循环单向链表以及循环双链表。 +* 遍历一个循环单链表,直到到达开始的同一个节点。循环单链表类似于链表,但它没有开始也没有结束。任何节点的下一部分都不存在NULL值。 + +![](循环单向链表.png) + +## 2 数据存储和实现 + +### 数据存储 + +链表的最后一个节点包含链表的第一个节点的地址。 + +![](2021-03-12-21-21-07.png) + +### 数据实现 + +* 链表通过结构体和指针实现 +```C++ +struct node +{ + int data; + struct node *next; +}; +struct node *head, *ptr; +ptr = (struct node *)malloc(sizeof(struct node *)); +``` +* C++ STL提供了链表的实现 + +``` +#include + +list li; +forward_list li; +``` + +## 3 操作 + + +### 基本操作 +* 创建 +* 遍历、搜索 +* 插入 +* 删除 + + +### 实现 +```C++ +#include +#include +struct node +{ + int data; + struct node *next; +}; +struct node *head; + +void beginsert(); +void lastinsert(); +void randominsert(); +void begin_delete(); +void last_delete(); +void random_delete(); +void display(); +void search(); +int main() +{ + int choice = 0; + while (choice != 7) + { + printf("*********Main Menu*********\n"); + printf("Choose one option from the following list ...\n"); + printf("===============================================\n"); + printf("1.Insert in begining\n2.Insert at last\n"); + printf("3.Delete from Beginning\n4.Delete from last\n"); + printf("5.Search for an element\n6.Show\n7.Exit\n"); + printf("Enter your choice?\n"); + scanf("%d", &choice); + switch (choice) + { + case 1: + beginsert(); + break; + case 2: + lastinsert(); + break; + case 3: + begin_delete(); + break; + case 4: + last_delete(); + break; + case 5: + search(); + break; + case 6: + display(); + break; + case 7: + exit(0); + break; + default: + printf("Please enter valid choice.."); + } + } +} +void beginsert() +{ + struct node *ptr, *temp; + int item; + ptr = (struct node *)malloc(sizeof(struct node)); + if (ptr == NULL) + { + printf("OVERFLOW"); + } + else + { + printf("Enter the node data?"); + scanf("%d", &item); + ptr->data = item; + if (head == NULL) + { + head = ptr; + ptr->next = head; + } + else + { + temp = head; + while (temp->next != head) + temp = temp->next; + ptr->next = head; + temp->next = ptr; + head = ptr; + } + printf("node inserted\n"); + } + +} +void lastinsert() +{ + struct node *ptr, *temp; + int item; + ptr = (struct node *)malloc(sizeof(struct node)); + if (ptr == NULL) + { + printf("OVERFLOW\n"); + } + else + { + printf("Enter Data?"); + scanf("%d", &item); + ptr->data = item; + if (head == NULL) + { + head = ptr; + ptr->next = head; + } + else + { + temp = head; + while (temp->next != head) + { + temp = temp->next; + } + temp->next = ptr; + ptr->next = head; + } + + printf("node inserted\n"); + } + +} + +void begin_delete() +{ + struct node *ptr; + if (head == NULL) + { + printf("UNDERFLOW"); + } + else if (head->next == head) + { + head = NULL; + free(head); + printf("node deleted\n"); + } + + else + { + ptr = head; + while (ptr->next != head) + ptr = ptr->next; + ptr->next = head->next; + free(head); + head = ptr->next; + printf("node deleted\n"); + + } +} +void last_delete() +{ + struct node *ptr, *preptr; + if (head == NULL) + { + printf("UNDERFLOW"); + } + else if (head->next == head) + { + head = NULL; + free(head); + printf("node deleted\n"); + + } + else + { + ptr = head; + while (ptr->next != head) + { + preptr = ptr; + ptr = ptr->next; + } + preptr->next = ptr->next; + free(ptr); + printf("node deleted\n"); + + } +} + +void search() +{ + struct node *ptr; + int item, i = 0, flag = 1; + ptr = head; + if (ptr == NULL) + { + printf("Empty List\n"); + } + else + { + printf("Enter item which you want to search?\n"); + scanf("%d", &item); + if (head->data == item) + { + printf("item found at location %d", i + 1); + flag = 0; + } + else + { + while (ptr->next != head) + { + if (ptr->data == item) + { + printf("item found at location %d ", i + 1); + flag = 0; + break; + } + else + { + flag = 1; + } + i++; + ptr = ptr->next; + } + } + if (flag != 0) + { + printf("Item not found\n"); + } + } + +} + +void display() +{ + struct node *ptr; + ptr = head; + if (head == NULL) + { + printf("nothing to print"); + } + else + { + printf("printing values ... \n"); + + while (ptr->next != head) + { + + printf("%d\n", ptr->data); + ptr = ptr->next; + } + printf("%d\n", ptr->data); + } + +} +``` diff --git a/数据结构/2.3.cpp b/数据结构/2.3.cpp new file mode 100644 index 00000000..205350f3 --- /dev/null +++ b/数据结构/2.3.cpp @@ -0,0 +1,246 @@ +#include +#include +struct node +{ + int data; + struct node *next; +}; +struct node *head; + +void beginsert(); +void lastinsert(); +void randominsert(); +void begin_delete(); +void last_delete(); +void random_delete(); +void display(); +void search(); +int main() +{ + int choice = 0; + while (choice != 7) + { + printf("*********Main Menu*********\n"); + printf("Choose one option from the following list ...\n"); + printf("===============================================\n"); + printf("1.Insert in begining\n2.Insert at last\n"); + printf("3.Delete from Beginning\n4.Delete from last\n"); + printf("5.Search for an element\n6.Show\n7.Exit\n"); + printf("Enter your choice?\n"); + scanf("%d", &choice); + switch (choice) + { + case 1: + beginsert(); + break; + case 2: + lastinsert(); + break; + case 3: + begin_delete(); + break; + case 4: + last_delete(); + break; + case 5: + search(); + break; + case 6: + display(); + break; + case 7: + exit(0); + break; + default: + printf("Please enter valid choice.."); + } + } +} +void beginsert() +{ + struct node *ptr, *temp; + int item; + ptr = (struct node *)malloc(sizeof(struct node)); + if (ptr == NULL) + { + printf("OVERFLOW"); + } + else + { + printf("Enter the node data?"); + scanf("%d", &item); + ptr->data = item; + if (head == NULL) + { + head = ptr; + ptr->next = head; + } + else + { + temp = head; + while (temp->next != head) + temp = temp->next; + ptr->next = head; + temp->next = ptr; + head = ptr; + } + printf("node inserted\n"); + } + +} +void lastinsert() +{ + struct node *ptr, *temp; + int item; + ptr = (struct node *)malloc(sizeof(struct node)); + if (ptr == NULL) + { + printf("OVERFLOW\n"); + } + else + { + printf("Enter Data?"); + scanf("%d", &item); + ptr->data = item; + if (head == NULL) + { + head = ptr; + ptr->next = head; + } + else + { + temp = head; + while (temp->next != head) + { + temp = temp->next; + } + temp->next = ptr; + ptr->next = head; + } + + printf("node inserted\n"); + } + +} + +void begin_delete() +{ + struct node *ptr; + if (head == NULL) + { + printf("UNDERFLOW"); + } + else if (head->next == head) + { + head = NULL; + free(head); + printf("node deleted\n"); + } + + else + { + ptr = head; + while (ptr->next != head) + ptr = ptr->next; + ptr->next = head->next; + free(head); + head = ptr->next; + printf("node deleted\n"); + + } +} +void last_delete() +{ + struct node *ptr, *preptr; + if (head == NULL) + { + printf("UNDERFLOW"); + } + else if (head->next == head) + { + head = NULL; + free(head); + printf("node deleted\n"); + + } + else + { + ptr = head; + while (ptr->next != head) + { + preptr = ptr; + ptr = ptr->next; + } + preptr->next = ptr->next; + free(ptr); + printf("node deleted\n"); + + } +} + +void search() +{ + struct node *ptr; + int item, i = 0, flag = 1; + ptr = head; + if (ptr == NULL) + { + printf("Empty List\n"); + } + else + { + printf("Enter item which you want to search?\n"); + scanf("%d", &item); + if (head->data == item) + { + printf("item found at location %d", i + 1); + flag = 0; + } + else + { + while (ptr->next != head) + { + if (ptr->data == item) + { + printf("item found at location %d ", i + 1); + flag = 0; + break; + } + else + { + flag = 1; + } + i++; + ptr = ptr->next; + } + } + if (flag != 0) + { + printf("Item not found\n"); + } + } + +} + +void display() +{ + struct node *ptr; + ptr = head; + if (head == NULL) + { + printf("nothing to print"); + } + else + { + printf("printing values ... \n"); + + while (ptr->next != head) + { + + printf("%d\n", ptr->data); + ptr = ptr->next; + } + printf("%d\n", ptr->data); + } + +} \ No newline at end of file diff --git a/数据结构/2.4 循环双链表.md b/数据结构/2.4 循环双链表.md new file mode 100644 index 00000000..a1d7938f --- /dev/null +++ b/数据结构/2.4 循环双链表.md @@ -0,0 +1,298 @@ +# 循环双向链表 + + +## 1 简介 +### 概念 + +* 循环双向链表是一种更复杂的数据结构类型,它的节点包含指向其前一节点以及下一节点的指针。 +* 循环双向链表在任何节点中都不包含NULL。 +* 链表的最后一个节点包含列表的第一个节点的地址。 +* 链表的第一个节点还包含的前一个指针是指向最后一个节点的地址。 + +![](2021-03-12-21-22-43.png) + + +## 2 数据存储和实现 +### 数据存储 + +* 起始节点包含最后一个(也是前一个)节点的地址,即8和下一个节点,即4。链表的最后一个节点,存储在地址8并包含数据6,包含链表的第一个节点的地址 + +![](循环双向链表内存.png) + +### 数据实现 + +* 使用结构体和指针实现。 +```C +struct node +{ + struct node *prev; + int data; + struct node *next; +} +``` +* 使用STL中的list实现 + +``` +#include +list li; +``` +## 3 操作 +### 基本操作 +* 创建 +* 遍历、搜索 +* 插入 +* 删除 + + +### 实现 +``` +#include +#include +struct node +{ + struct node *prev; + struct node *next; + int data; +}; +struct node *head; +void insertion_beginning(); +void insertion_last(); +void deletion_beginning(); +void deletion_last(); +void display(); +void search(); +int main() +{ + int choice = 0; + while (choice != 9) + { + printf("*********Main Menu*********\n"); + printf("Choose one option from the following list ...\n"); + printf("===============================================\n"); + printf("1.Insert in Beginning\n2.Insert at last\n"); + printf("3.Delete from Beginning\n4.Delete from last\n"); + printf("5.Search\n6.Show\n7.Exit\n"); + printf("Enter your choice?\n"); + scanf("\n%d", &choice); + switch (choice) + { + case 1: + insertion_beginning(); + break; + case 2: + insertion_last(); + break; + case 3: + deletion_beginning(); + break; + case 4: + deletion_last(); + break; + case 5: + search(); + break; + case 6: + display(); + break; + case 7: + exit(0); + break; + default: + printf("Please enter valid choice.."); + } + } +} +void insertion_beginning() +{ + struct node *ptr, *temp; + int item; + ptr = (struct node *)malloc(sizeof(struct node)); + if (ptr == NULL) + { + printf("OVERFLOW"); + } + else + { + printf("Enter Item value"); + scanf("%d", &item); + ptr->data = item; + if (head == NULL) + { + head = ptr; + ptr->next = head; + ptr->prev = head; + } + else + { + temp = head; + while (temp->next != head) + { + temp = temp->next; + } + temp->next = ptr; + ptr->prev = temp; + head->prev = ptr; + ptr->next = head; + head = ptr; + } + printf("Node inserted\n"); + } + +} +void insertion_last() +{ + struct node *ptr, *temp; + int item; + ptr = (struct node *) malloc(sizeof(struct node)); + if (ptr == NULL) + { + printf("OVERFLOW"); + } + else + { + printf("Enter value"); + scanf("%d", &item); + ptr->data = item; + if (head == NULL) + { + head = ptr; + ptr->next = head; + ptr->prev = head; + } + else + { + temp = head; + while (temp->next != head) + { + temp = temp->next; + } + temp->next = ptr; + ptr->prev = temp; + head->prev = ptr; + ptr->next = head; + } + } + printf("node inserted\n"); +} + +void deletion_beginning() +{ + struct node *temp; + if (head == NULL) + { + printf("UNDERFLOW"); + } + else if (head->next == head) + { + head = NULL; + free(head); + printf("node deleted\n"); + } + else + { + temp = head; + while (temp->next != head) + { + temp = temp->next; + } + temp->next = head->next; + head->next->prev = temp; + free(head); + head = temp->next; + } + +} +void deletion_last() +{ + struct node *ptr; + if (head == NULL) + { + printf("UNDERFLOW"); + } + else if (head->next == head) + { + head = NULL; + free(head); + printf("node deleted\n"); + } + else + { + ptr = head; + if (ptr->next != head) + { + ptr = ptr->next; + } + ptr->prev->next = head; + head->prev = ptr->prev; + free(ptr); + printf("node deleted\n"); + } +} + +void display() +{ + struct node *ptr; + ptr = head; + if (head == NULL) + { + printf("nothing to print"); + } + else + { + printf("printing values ... \n"); + + while (ptr->next != head) + { + + printf("%d\n", ptr->data); + ptr = ptr->next; + } + printf("%d\n", ptr->data); + } + +} + +void search() +{ + struct node *ptr; + int item, i = 0, flag = 1; + ptr = head; + if (ptr == NULL) + { + printf("Empty List\n"); + } + else + { + printf("Enter item which you want to search?\n"); + scanf("%d", &item); + if (head->data == item) + { + printf("item found at location %d", i + 1); + flag = 0; + } + else + { + while (ptr->next != head) + { + if (ptr->data == item) + { + printf("item found at location %d ", i + 1); + flag = 0; + break; + } + else + { + flag = 1; + } + i++; + ptr = ptr->next; + } + } + if (flag != 0) + { + printf("Item not found\n"); + } + } + +} +``` \ No newline at end of file diff --git a/数据结构/2.4.cpp b/数据结构/2.4.cpp new file mode 100644 index 00000000..19b6d129 --- /dev/null +++ b/数据结构/2.4.cpp @@ -0,0 +1,249 @@ +#include +#include +struct node +{ + struct node *prev; + struct node *next; + int data; +}; +struct node *head; +void insertion_beginning(); +void insertion_last(); +void deletion_beginning(); +void deletion_last(); +void display(); +void search(); +int main() +{ + int choice = 0; + while (choice != 9) + { + printf("*********Main Menu*********\n"); + printf("Choose one option from the following list ...\n"); + printf("===============================================\n"); + printf("1.Insert in Beginning\n2.Insert at last\n"); + printf("3.Delete from Beginning\n4.Delete from last\n"); + printf("5.Search\n6.Show\n7.Exit\n"); + printf("Enter your choice?\n"); + scanf("\n%d", &choice); + switch (choice) + { + case 1: + insertion_beginning(); + break; + case 2: + insertion_last(); + break; + case 3: + deletion_beginning(); + break; + case 4: + deletion_last(); + break; + case 5: + search(); + break; + case 6: + display(); + break; + case 7: + exit(0); + break; + default: + printf("Please enter valid choice.."); + } + } +} +void insertion_beginning() +{ + struct node *ptr, *temp; + int item; + ptr = (struct node *)malloc(sizeof(struct node)); + if (ptr == NULL) + { + printf("OVERFLOW"); + } + else + { + printf("Enter Item value"); + scanf("%d", &item); + ptr->data = item; + if (head == NULL) + { + head = ptr; + ptr->next = head; + ptr->prev = head; + } + else + { + temp = head; + while (temp->next != head) + { + temp = temp->next; + } + temp->next = ptr; + ptr->prev = temp; + head->prev = ptr; + ptr->next = head; + head = ptr; + } + printf("Node inserted\n"); + } + +} +void insertion_last() +{ + struct node *ptr, *temp; + int item; + ptr = (struct node *) malloc(sizeof(struct node)); + if (ptr == NULL) + { + printf("OVERFLOW"); + } + else + { + printf("Enter value"); + scanf("%d", &item); + ptr->data = item; + if (head == NULL) + { + head = ptr; + ptr->next = head; + ptr->prev = head; + } + else + { + temp = head; + while (temp->next != head) + { + temp = temp->next; + } + temp->next = ptr; + ptr->prev = temp; + head->prev = ptr; + ptr->next = head; + } + } + printf("node inserted\n"); +} + +void deletion_beginning() +{ + struct node *temp; + if (head == NULL) + { + printf("UNDERFLOW"); + } + else if (head->next == head) + { + head = NULL; + free(head); + printf("node deleted\n"); + } + else + { + temp = head; + while (temp->next != head) + { + temp = temp->next; + } + temp->next = head->next; + head->next->prev = temp; + free(head); + head = temp->next; + } + +} +void deletion_last() +{ + struct node *ptr; + if (head == NULL) + { + printf("UNDERFLOW"); + } + else if (head->next == head) + { + head = NULL; + free(head); + printf("node deleted\n"); + } + else + { + ptr = head; + if (ptr->next != head) + { + ptr = ptr->next; + } + ptr->prev->next = head; + head->prev = ptr->prev; + free(ptr); + printf("node deleted\n"); + } +} + +void display() +{ + struct node *ptr; + ptr = head; + if (head == NULL) + { + printf("nothing to print"); + } + else + { + printf("printing values ... \n"); + + while (ptr->next != head) + { + + printf("%d\n", ptr->data); + ptr = ptr->next; + } + printf("%d\n", ptr->data); + } + +} + +void search() +{ + struct node *ptr; + int item, i = 0, flag = 1; + ptr = head; + if (ptr == NULL) + { + printf("Empty List\n"); + } + else + { + printf("Enter item which you want to search?\n"); + scanf("%d", &item); + if (head->data == item) + { + printf("item found at location %d", i + 1); + flag = 0; + } + else + { + while (ptr->next != head) + { + if (ptr->data == item) + { + printf("item found at location %d ", i + 1); + flag = 0; + break; + } + else + { + flag = 1; + } + i++; + ptr = ptr->next; + } + } + if (flag != 0) + { + printf("Item not found\n"); + } + } + +} \ No newline at end of file diff --git a/数据结构/2021-03-12-17-07-01.png b/数据结构/2021-03-12-17-07-01.png new file mode 100644 index 00000000..eeee6d45 Binary files /dev/null and b/数据结构/2021-03-12-17-07-01.png differ diff --git a/数据结构/2021-03-12-20-47-17.png b/数据结构/2021-03-12-20-47-17.png new file mode 100644 index 00000000..a6a0afff Binary files /dev/null and b/数据结构/2021-03-12-20-47-17.png differ diff --git a/数据结构/2021-03-12-20-55-32.png b/数据结构/2021-03-12-20-55-32.png new file mode 100644 index 00000000..e195ce9a Binary files /dev/null and b/数据结构/2021-03-12-20-55-32.png differ diff --git a/数据结构/2021-03-12-20-57-19.png b/数据结构/2021-03-12-20-57-19.png new file mode 100644 index 00000000..47b66762 Binary files /dev/null and b/数据结构/2021-03-12-20-57-19.png differ diff --git a/数据结构/2021-03-12-20-57-51.png b/数据结构/2021-03-12-20-57-51.png new file mode 100644 index 00000000..924dcfb9 Binary files /dev/null and b/数据结构/2021-03-12-20-57-51.png differ diff --git a/数据结构/2021-03-12-20-58-14.png b/数据结构/2021-03-12-20-58-14.png new file mode 100644 index 00000000..1dc8151f Binary files /dev/null and b/数据结构/2021-03-12-20-58-14.png differ diff --git a/数据结构/2021-03-12-20-58-23.png b/数据结构/2021-03-12-20-58-23.png new file mode 100644 index 00000000..f6b4bf34 Binary files /dev/null and b/数据结构/2021-03-12-20-58-23.png differ diff --git a/数据结构/2021-03-12-21-00-33.png b/数据结构/2021-03-12-21-00-33.png new file mode 100644 index 00000000..8b70ce13 Binary files /dev/null and b/数据结构/2021-03-12-21-00-33.png differ diff --git a/数据结构/2021-03-12-21-01-53.png b/数据结构/2021-03-12-21-01-53.png new file mode 100644 index 00000000..9d603027 Binary files /dev/null and b/数据结构/2021-03-12-21-01-53.png differ diff --git a/数据结构/2021-03-12-21-08-07.png b/数据结构/2021-03-12-21-08-07.png new file mode 100644 index 00000000..841ec134 Binary files /dev/null and b/数据结构/2021-03-12-21-08-07.png differ diff --git a/数据结构/2021-03-12-21-14-50.png b/数据结构/2021-03-12-21-14-50.png new file mode 100644 index 00000000..ecaca50e Binary files /dev/null and b/数据结构/2021-03-12-21-14-50.png differ diff --git a/数据结构/2021-03-12-21-14-57.png b/数据结构/2021-03-12-21-14-57.png new file mode 100644 index 00000000..d35cc504 Binary files /dev/null and b/数据结构/2021-03-12-21-14-57.png differ diff --git a/数据结构/2021-03-12-21-21-07.png b/数据结构/2021-03-12-21-21-07.png new file mode 100644 index 00000000..74b259a0 Binary files /dev/null and b/数据结构/2021-03-12-21-21-07.png differ diff --git a/数据结构/2021-03-12-21-22-43.png b/数据结构/2021-03-12-21-22-43.png new file mode 100644 index 00000000..400ed25e Binary files /dev/null and b/数据结构/2021-03-12-21-22-43.png differ diff --git a/数据结构/2021-03-12-21-29-39.png b/数据结构/2021-03-12-21-29-39.png new file mode 100644 index 00000000..414b01c6 Binary files /dev/null and b/数据结构/2021-03-12-21-29-39.png differ diff --git a/数据结构/2021-03-12-21-29-49.png b/数据结构/2021-03-12-21-29-49.png new file mode 100644 index 00000000..176ec979 Binary files /dev/null and b/数据结构/2021-03-12-21-29-49.png differ diff --git a/数据结构/2021-03-12-21-36-39.png b/数据结构/2021-03-12-21-36-39.png new file mode 100644 index 00000000..1ad327fa Binary files /dev/null and b/数据结构/2021-03-12-21-36-39.png differ diff --git a/数据结构/2021-03-12-21-43-29.png b/数据结构/2021-03-12-21-43-29.png new file mode 100644 index 00000000..ddb7361c Binary files /dev/null and b/数据结构/2021-03-12-21-43-29.png differ diff --git a/数据结构/2021-03-12-22-05-59.png b/数据结构/2021-03-12-22-05-59.png new file mode 100644 index 00000000..deee2318 Binary files /dev/null and b/数据结构/2021-03-12-22-05-59.png differ diff --git a/数据结构/2021-03-12-22-07-20.png b/数据结构/2021-03-12-22-07-20.png new file mode 100644 index 00000000..72cca99c Binary files /dev/null and b/数据结构/2021-03-12-22-07-20.png differ diff --git a/数据结构/2021-03-12-22-10-38.png b/数据结构/2021-03-12-22-10-38.png new file mode 100644 index 00000000..0c41b433 Binary files /dev/null and b/数据结构/2021-03-12-22-10-38.png differ diff --git a/数据结构/2021-03-12-22-12-08.png b/数据结构/2021-03-12-22-12-08.png new file mode 100644 index 00000000..0ef87695 Binary files /dev/null and b/数据结构/2021-03-12-22-12-08.png differ diff --git a/数据结构/2021-03-12-22-12-52.png b/数据结构/2021-03-12-22-12-52.png new file mode 100644 index 00000000..f5a9ec82 Binary files /dev/null and b/数据结构/2021-03-12-22-12-52.png differ diff --git a/数据结构/2021-03-12-22-16-50.png b/数据结构/2021-03-12-22-16-50.png new file mode 100644 index 00000000..427eb90a Binary files /dev/null and b/数据结构/2021-03-12-22-16-50.png differ diff --git a/数据结构/2021-03-12-22-17-33.png b/数据结构/2021-03-12-22-17-33.png new file mode 100644 index 00000000..1da0a6a0 Binary files /dev/null and b/数据结构/2021-03-12-22-17-33.png differ diff --git a/数据结构/2021-03-12-22-46-59.png b/数据结构/2021-03-12-22-46-59.png new file mode 100644 index 00000000..4937bd00 Binary files /dev/null and b/数据结构/2021-03-12-22-46-59.png differ diff --git a/数据结构/2021-03-12-22-48-28.png b/数据结构/2021-03-12-22-48-28.png new file mode 100644 index 00000000..1b9beaec Binary files /dev/null and b/数据结构/2021-03-12-22-48-28.png differ diff --git a/数据结构/2021-03-12-22-53-43.png b/数据结构/2021-03-12-22-53-43.png new file mode 100644 index 00000000..0242e446 Binary files /dev/null and b/数据结构/2021-03-12-22-53-43.png differ diff --git a/数据结构/2021-03-12-23-35-36.png b/数据结构/2021-03-12-23-35-36.png new file mode 100644 index 00000000..c3d9e947 Binary files /dev/null and b/数据结构/2021-03-12-23-35-36.png differ diff --git a/数据结构/2021-03-12-23-39-19.png b/数据结构/2021-03-12-23-39-19.png new file mode 100644 index 00000000..0b84fb80 Binary files /dev/null and b/数据结构/2021-03-12-23-39-19.png differ diff --git a/数据结构/2021-03-12-23-47-41.png b/数据结构/2021-03-12-23-47-41.png new file mode 100644 index 00000000..c021ecb8 Binary files /dev/null and b/数据结构/2021-03-12-23-47-41.png differ diff --git a/数据结构/2021-03-12-23-47-46.png b/数据结构/2021-03-12-23-47-46.png new file mode 100644 index 00000000..bcd09220 Binary files /dev/null and b/数据结构/2021-03-12-23-47-46.png differ diff --git a/数据结构/2021-03-12-23-47-52.png b/数据结构/2021-03-12-23-47-52.png new file mode 100644 index 00000000..c70cbe2b Binary files /dev/null and b/数据结构/2021-03-12-23-47-52.png differ diff --git a/数据结构/2021-03-12-23-51-37.png b/数据结构/2021-03-12-23-51-37.png new file mode 100644 index 00000000..924a8721 Binary files /dev/null and b/数据结构/2021-03-12-23-51-37.png differ diff --git a/数据结构/2021-03-12-23-51-43.png b/数据结构/2021-03-12-23-51-43.png new file mode 100644 index 00000000..5e70031b Binary files /dev/null and b/数据结构/2021-03-12-23-51-43.png differ diff --git a/数据结构/2021-03-12-23-51-51.png b/数据结构/2021-03-12-23-51-51.png new file mode 100644 index 00000000..8bb9cd65 Binary files /dev/null and b/数据结构/2021-03-12-23-51-51.png differ diff --git a/数据结构/2021-03-12-23-58-07.png b/数据结构/2021-03-12-23-58-07.png new file mode 100644 index 00000000..2b795de5 Binary files /dev/null and b/数据结构/2021-03-12-23-58-07.png differ diff --git a/数据结构/2021-03-12-23-58-14.png b/数据结构/2021-03-12-23-58-14.png new file mode 100644 index 00000000..b0937c72 Binary files /dev/null and b/数据结构/2021-03-12-23-58-14.png differ diff --git a/数据结构/2021-03-12-23-58-20.png b/数据结构/2021-03-12-23-58-20.png new file mode 100644 index 00000000..0e5323f4 Binary files /dev/null and b/数据结构/2021-03-12-23-58-20.png differ diff --git a/数据结构/2021-03-12-23-58-29.png b/数据结构/2021-03-12-23-58-29.png new file mode 100644 index 00000000..0e5323f4 Binary files /dev/null and b/数据结构/2021-03-12-23-58-29.png differ diff --git a/数据结构/2021-03-13-00-03-01.png b/数据结构/2021-03-13-00-03-01.png new file mode 100644 index 00000000..84da3353 Binary files /dev/null and b/数据结构/2021-03-13-00-03-01.png differ diff --git a/数据结构/2021-03-13-00-03-07.png b/数据结构/2021-03-13-00-03-07.png new file mode 100644 index 00000000..42610f6f Binary files /dev/null and b/数据结构/2021-03-13-00-03-07.png differ diff --git a/数据结构/2021-03-13-00-03-13.png b/数据结构/2021-03-13-00-03-13.png new file mode 100644 index 00000000..ad38047b Binary files /dev/null and b/数据结构/2021-03-13-00-03-13.png differ diff --git a/数据结构/2021-03-13-00-03-30.png b/数据结构/2021-03-13-00-03-30.png new file mode 100644 index 00000000..0986a155 Binary files /dev/null and b/数据结构/2021-03-13-00-03-30.png differ diff --git a/数据结构/2021-03-13-00-07-04.png b/数据结构/2021-03-13-00-07-04.png new file mode 100644 index 00000000..17f938c9 Binary files /dev/null and b/数据结构/2021-03-13-00-07-04.png differ diff --git a/数据结构/2021-03-13-00-15-09.png b/数据结构/2021-03-13-00-15-09.png new file mode 100644 index 00000000..7e54e26a Binary files /dev/null and b/数据结构/2021-03-13-00-15-09.png differ diff --git a/数据结构/2021-03-13-00-15-45.png b/数据结构/2021-03-13-00-15-45.png new file mode 100644 index 00000000..792294bf Binary files /dev/null and b/数据结构/2021-03-13-00-15-45.png differ diff --git a/数据结构/2021-03-13-00-16-39.png b/数据结构/2021-03-13-00-16-39.png new file mode 100644 index 00000000..11b13b05 Binary files /dev/null and b/数据结构/2021-03-13-00-16-39.png differ diff --git a/数据结构/2021-03-13-00-18-58.png b/数据结构/2021-03-13-00-18-58.png new file mode 100644 index 00000000..1fd4648a Binary files /dev/null and b/数据结构/2021-03-13-00-18-58.png differ diff --git a/数据结构/2021-03-13-00-23-57.png b/数据结构/2021-03-13-00-23-57.png new file mode 100644 index 00000000..bfe17e49 Binary files /dev/null and b/数据结构/2021-03-13-00-23-57.png differ diff --git a/数据结构/2021-03-13-00-25-14.png b/数据结构/2021-03-13-00-25-14.png new file mode 100644 index 00000000..f20a185e Binary files /dev/null and b/数据结构/2021-03-13-00-25-14.png differ diff --git a/数据结构/2021-03-13-00-29-43.png b/数据结构/2021-03-13-00-29-43.png new file mode 100644 index 00000000..ed58a5a0 Binary files /dev/null and b/数据结构/2021-03-13-00-29-43.png differ diff --git a/数据结构/2021-03-13-00-30-58.png b/数据结构/2021-03-13-00-30-58.png new file mode 100644 index 00000000..0e225e0a Binary files /dev/null and b/数据结构/2021-03-13-00-30-58.png differ diff --git a/数据结构/2021-03-13-12-51-08.png b/数据结构/2021-03-13-12-51-08.png new file mode 100644 index 00000000..99412fa4 Binary files /dev/null and b/数据结构/2021-03-13-12-51-08.png differ diff --git a/数据结构/2021-03-13-12-53-35.png b/数据结构/2021-03-13-12-53-35.png new file mode 100644 index 00000000..4292c701 Binary files /dev/null and b/数据结构/2021-03-13-12-53-35.png differ diff --git a/数据结构/2021-03-13-14-06-44.png b/数据结构/2021-03-13-14-06-44.png new file mode 100644 index 00000000..64538a32 Binary files /dev/null and b/数据结构/2021-03-13-14-06-44.png differ diff --git a/数据结构/2021-03-13-14-09-24.png b/数据结构/2021-03-13-14-09-24.png new file mode 100644 index 00000000..32396b7e Binary files /dev/null and b/数据结构/2021-03-13-14-09-24.png differ diff --git a/数据结构/2021-03-13-14-10-25.png b/数据结构/2021-03-13-14-10-25.png new file mode 100644 index 00000000..5cbf4ea7 Binary files /dev/null and b/数据结构/2021-03-13-14-10-25.png differ diff --git a/数据结构/2021-03-13-14-10-40.png b/数据结构/2021-03-13-14-10-40.png new file mode 100644 index 00000000..642017c9 Binary files /dev/null and b/数据结构/2021-03-13-14-10-40.png differ diff --git a/数据结构/2021-03-13-14-25-55.png b/数据结构/2021-03-13-14-25-55.png new file mode 100644 index 00000000..cd363abc Binary files /dev/null and b/数据结构/2021-03-13-14-25-55.png differ diff --git a/数据结构/2021-03-13-14-28-34.png b/数据结构/2021-03-13-14-28-34.png new file mode 100644 index 00000000..f0827e73 Binary files /dev/null and b/数据结构/2021-03-13-14-28-34.png differ diff --git a/数据结构/2021-03-13-14-29-50.png b/数据结构/2021-03-13-14-29-50.png new file mode 100644 index 00000000..4eeb2245 Binary files /dev/null and b/数据结构/2021-03-13-14-29-50.png differ diff --git a/数据结构/2021-03-13-14-30-35.png b/数据结构/2021-03-13-14-30-35.png new file mode 100644 index 00000000..cebb9c76 Binary files /dev/null and b/数据结构/2021-03-13-14-30-35.png differ diff --git a/数据结构/2021-03-13-14-43-51.png b/数据结构/2021-03-13-14-43-51.png new file mode 100644 index 00000000..45a94a5a Binary files /dev/null and b/数据结构/2021-03-13-14-43-51.png differ diff --git a/数据结构/2021-03-13-14-44-34.png b/数据结构/2021-03-13-14-44-34.png new file mode 100644 index 00000000..dede2e17 Binary files /dev/null and b/数据结构/2021-03-13-14-44-34.png differ diff --git a/数据结构/2021-03-13-15-23-14.png b/数据结构/2021-03-13-15-23-14.png new file mode 100644 index 00000000..30642b8b Binary files /dev/null and b/数据结构/2021-03-13-15-23-14.png differ diff --git a/数据结构/2021-03-13-15-23-24.png b/数据结构/2021-03-13-15-23-24.png new file mode 100644 index 00000000..85d34812 Binary files /dev/null and b/数据结构/2021-03-13-15-23-24.png differ diff --git a/数据结构/2021-03-13-15-25-15.png b/数据结构/2021-03-13-15-25-15.png new file mode 100644 index 00000000..4e63d271 Binary files /dev/null and b/数据结构/2021-03-13-15-25-15.png differ diff --git a/数据结构/2021-03-13-15-31-06.png b/数据结构/2021-03-13-15-31-06.png new file mode 100644 index 00000000..c897ade5 Binary files /dev/null and b/数据结构/2021-03-13-15-31-06.png differ diff --git a/数据结构/2021-03-13-15-43-40.png b/数据结构/2021-03-13-15-43-40.png new file mode 100644 index 00000000..a52dea51 Binary files /dev/null and b/数据结构/2021-03-13-15-43-40.png differ diff --git a/数据结构/2021-03-13-15-43-53.png b/数据结构/2021-03-13-15-43-53.png new file mode 100644 index 00000000..15145356 Binary files /dev/null and b/数据结构/2021-03-13-15-43-53.png differ diff --git a/数据结构/2021-03-13-16-01-31.png b/数据结构/2021-03-13-16-01-31.png new file mode 100644 index 00000000..40d1edb3 Binary files /dev/null and b/数据结构/2021-03-13-16-01-31.png differ diff --git a/数据结构/2021-03-13-16-02-28.png b/数据结构/2021-03-13-16-02-28.png new file mode 100644 index 00000000..b93821ba Binary files /dev/null and b/数据结构/2021-03-13-16-02-28.png differ diff --git a/数据结构/2021-03-13-16-12-10.png b/数据结构/2021-03-13-16-12-10.png new file mode 100644 index 00000000..36e1a7a3 Binary files /dev/null and b/数据结构/2021-03-13-16-12-10.png differ diff --git a/数据结构/2021-03-13-16-28-06.png b/数据结构/2021-03-13-16-28-06.png new file mode 100644 index 00000000..b57de30e Binary files /dev/null and b/数据结构/2021-03-13-16-28-06.png differ diff --git a/数据结构/2021-03-13-16-32-02.png b/数据结构/2021-03-13-16-32-02.png new file mode 100644 index 00000000..31563e59 Binary files /dev/null and b/数据结构/2021-03-13-16-32-02.png differ diff --git a/数据结构/2021-03-13-16-32-17.png b/数据结构/2021-03-13-16-32-17.png new file mode 100644 index 00000000..b5d0fc6a Binary files /dev/null and b/数据结构/2021-03-13-16-32-17.png differ diff --git a/数据结构/2021-03-13-17-02-40.png b/数据结构/2021-03-13-17-02-40.png new file mode 100644 index 00000000..20e93359 Binary files /dev/null and b/数据结构/2021-03-13-17-02-40.png differ diff --git a/数据结构/3 栈.md b/数据结构/3 栈.md new file mode 100644 index 00000000..f2373c18 --- /dev/null +++ b/数据结构/3 栈.md @@ -0,0 +1,255 @@ +# 堆栈 + +## 1 简介 + +### 概念 +* 堆栈(stack)又称为栈,是计算机科学中一种特殊的串列形式的抽象数据类型, +* 堆栈(Stack)是一个有序列表,它只能在顶端执行插入和删除。 +* 堆栈(Stack)是一个递归数据结构,具有指向其顶部元素的指针。 +* 堆栈为后进先出(LIFO)列表,即首先插入堆栈的元素将最后从堆栈中删除. + +![](堆栈.png) + +### 应用 + +* 深度优先搜索与回溯 +* 递归 +* 表达式评估和转换 +* 解析 +* 浏览器 +* 编辑器 +* 树遍历 + + +### 特点 + +1. 先入后出,后入先出。 +2. 除头尾节点之外,每个元素有一个前驱,一个后继。 + + + +## 2 栈的操作 +### 基础操作 + +- 推入 push + - 将数据放入堆栈的顶端(数组形式或串列形式),堆栈顶端 top 指针加一。 +![](2021-03-12-21-29-39.png) +- 弹出 pop + - 将顶端数据数据输出(回传),堆栈顶端数据减一。 +![](2021-03-12-21-29-49.png) +- 查看 top + - 查看栈顶的元素而不删除它们。 + + +> 以后有时间来实现这些数据结构 + + +## 3 栈的实现 + +### 数组实现 +```C++ +#include +int stack[100], i, j, choice = 0, n, top = -1; +void push(); +void pop(); +void show(); +void main() +{ + + printf("Enter the number of elements in the stack "); + scanf("%d", &n); + printf("*********Stack operations using array*********"); + printf("----------------------------------------------\n"); + while (choice != 4) + { + printf("Chose one from the below options...\n"); + printf("1.Push\n2.Pop\n3.Show\n4.Exit"); + printf("Enter your choice \n"); + scanf("%d", &choice); + switch (choice) + { + case 1: + { + push(); + break; + } + case 2: + { + pop(); + break; + } + case 3: + { + show(); + break; + } + case 4: + { + printf("Exiting...."); + break; + } + default: + { + printf("Please Enter valid choice "); + } + }; + } +} + +void push() +{ + int val; + if (top == n) + printf("Overflow"); + else + { + printf("Enter the value?"); + scanf("%d", &val); + top = top + 1; + stack[top] = val; + } +} + +void pop() +{ + if (top == -1) + printf("Underflow"); + else + top = top - 1; +} +void show() +{ + for (i = top;i >= 0;i--) + { + printf("%d\n", stack[i]); + } + if (top == -1) + { + printf("Stack is empty"); + } +} +``` + +### 链表实现 + +```C++ +#include +#include +void push(); +void pop(); +void display(); +struct node +{ + int val; + struct node *next; +}; +struct node *head; + +void main() +{ + int choice = 0; + printf("*********Stack operations using linked list*********\n"); + printf("----------------------------------------------\n"); + while (choice != 4) + { + printf("Chose one from the below options...\n"); + printf("1.Push\n2.Pop\n3.Show\n4.Exit"); + printf("Enter your choice \n"); + scanf("%d", &choice); + switch (choice) + { + case 1: + { + push(); + break; + } + case 2: + { + pop(); + break; + } + case 3: + { + display(); + break; + } + case 4: + { + printf("Exiting...."); + break; + } + default: + { + printf("Please Enter valid choice "); + } + }; + } +} +void push() +{ + int val; + struct node *ptr = (struct node*)malloc(sizeof(struct node)); + if (ptr == NULL) + { + printf("not able to push the element"); + } + else + { + printf("Enter the value"); + scanf("%d", &val); + if (head == NULL) + { + ptr->val = val; + ptr->next = NULL; + head = ptr; + } + else + { + ptr->val = val; + ptr->next = head; + head = ptr; + + } + printf("Item pushed"); + + } +} + +void pop() +{ + int item; + struct node *ptr; + if (head == NULL) + { + printf("Underflow"); + } + else + { + item = head->val; + ptr = head; + head = head->next; + free(ptr); + printf("Item popped"); + + } +} +void display() +{ + int i; + struct node *ptr; + ptr = head; + if (ptr == NULL) + { + printf("Stack is empty\n"); + } + else + { + printf("Printing Stack elements \n"); + while (ptr != NULL) + { + printf("%d\n", ptr->val); + ptr = ptr->next; + } + } +} +``` \ No newline at end of file diff --git a/数据结构/4 队列.md b/数据结构/4 队列.md new file mode 100644 index 00000000..d4a38060 --- /dev/null +++ b/数据结构/4 队列.md @@ -0,0 +1,259 @@ +# 队列 + +## 1 简介 + +### 概念 +* 队列可以定义为有序列表。在一端执行插入操作rear,删除操作在另一端执行,称为front。 +* 队列被称为先进先出列表。 + +![](2021-03-12-21-36-39.png) + +### 应用 + +* 单个共享资源(如打印机,磁盘,CPU)的等待列表。 +* 异步数据传输。管道,文件IO,套接字。 +* 缓冲区. +* 操作系统中处理中断。 + +### 时间复杂度 + +| 时间复杂性 | 访问 | 搜索 | 插入 | 删除 | +|-------|------|------|------|------| +| 平均情况 | θ(n) | θ(n) | θ(1) | θ(1) | +| 最坏情况 | θ(n) | θ(n) | θ(1) | θ(1) | + + +## 2 队列的操作 +### 基本操作 +* 创建 +* 遍历(显示第一个元素) +* 插入 +* 删除 + +## 3 队列的实现 +### 队列的数组实现 +```C++ +#include +#include +#define maxsize 5 +void insert(); +void delete(); +void display(); +int front = -1, rear = -1; +int queue[maxsize]; +void main() +{ + int choice; + while (choice != 4) + { + printf("*************************Main Menu*****************************\n"); + printf("=================================================================\n"); + printf("1.insert an element\n2.Delete an element\n3.Display the queue\n4.Exit\n"); + printf("Enter your choice ?"); + scanf("%d", &choice); + switch (choice) + { + case 1: + insert(); + break; + case 2: + delete(); + break; + case 3: + display(); + break; + case 4: + exit(0); + break; + default: + printf("Enter valid choice??\n"); + } + } +} +void insert() +{ + int item; + printf("\nEnter the element\n"); + scanf("\n%d", &item); + if (rear == maxsize - 1) + { + printf("OVERFLOW\n"); + return; + } + if (front == -1 && rear == -1) + { + front = 0; + rear = 0; + } + else + { + rear = rear + 1; + } + queue[rear] = item; + printf("Value inserted "); + +} +void delete() +{ + int item; + if (front == -1 || front > rear) + { + printf("UNDERFLOW\n"); + return; + + } + else + { + item = queue[front]; + if (front == rear) + { + front = -1; + rear = -1; + } + else + { + front = front + 1; + } + printf("value deleted "); + } + + +} + +void display() +{ + int i; + if (rear == -1) + { + printf("Empty queue\n"); + } + else + { + printf("printing values .....\n"); + for (i = front;i <= rear;i++) + { + printf("\n%d\n", queue[i]); + } + } +} +``` +### 队列的链表实现 +```C++ +#include +#include +struct node +{ + int data; + struct node *next; +}; +struct node *front; +struct node *rear; +void insert(); +void delete(); +void display(); +void main() +{ + int choice; + while (choice != 4) + { + printf("*************************Main Menu*****************************\n"); + printf("=================================================================\n"); + printf("1.insert an element\n2.Delete an element\n3.Display the queue\n4.Exit\n"); + printf("Enter your choice ?"); + scanf("%d", &choice); + switch (choice) + { + case 1: + insert(); + break; + case 2: + delete(); + break; + case 3: + display(); + break; + case 4: + exit(0); + break; + default: + printf("Enter valid choice??\n"); + } + } +} +void insert() +{ + struct node *ptr; + int item; + + ptr = (struct node *) malloc(sizeof(struct node)); + if (ptr == NULL) + { + printf("OVERFLOW\n"); + return; + } + else + { + printf("Enter value?\n"); + scanf("%d", &item); + ptr->data = item; + if (front == NULL) + { + front = ptr; + rear = ptr; + front->next = NULL; + rear->next = NULL; + } + else + { + rear->next = ptr; + rear = ptr; + rear->next = NULL; + } + } +} +void delete () +{ + struct node *ptr; + if (front == NULL) + { + printf("UNDERFLOW\n"); + return; + } + else + { + ptr = front; + front = front->next; + free(ptr); + } +} +void display() +{ + struct node *ptr; + ptr = front; + if (front == NULL) + { + printf("Empty queue\n"); + } + else + { + printf("printing values .....\n"); + while (ptr != NULL) + { + printf("%d\n", ptr->data); + ptr = ptr->next; + } + } +} +``` + +### C++模板 + +``` +#include + +queue que; +``` + + + + diff --git a/数据结构/4.1 循环队列.md b/数据结构/4.1 循环队列.md new file mode 100644 index 00000000..a758dd62 --- /dev/null +++ b/数据结构/4.1 循环队列.md @@ -0,0 +1,141 @@ +# 循环队列 + +## 1 简介 +### 概念 + +* 在循环队列中,最后一个索引紧跟在第一个索引之后。 +* 当front = -1和rear = max-1时,循环队列将满。循环队列的实现类似于线性队列的实现。只有在插入和删除的情况下实现的逻辑部分与线性队列中的逻辑部分不同。 + +![](循环队列.png) + +### 时间复杂度 + +| 操作 | | +|-----------|------| +| Front | O(1) | +| Rear | O(1) | +| enQueue() | O(1) | +| deQueue() | O(1) | + + + + +## 2 循环队列的操作 +### 基本操作 +* 创建 +* 遍历、搜索、查找(显示第一个元素) +* 插入 +* 删除 + +## 3 循环队列的实现 + +### 数组实现 +``` +#include +#include +#define maxsize 5 +void insert(); +void delete(); +void display(); +int front = -1, rear = -1; +int queue[maxsize]; +void main() +{ + int choice; + while (choice != 4) + { + printf("*************************Main Menu*****************************\n"); + printf("=================================================================\n"); + printf("1.insert an element\n2.Delete an element\n3.Display the queue\n4.Exit\n"); + printf("Enter your choice ?"); + scanf("%d", &choice); + switch (choice) + { + case 1: + insert(); + break; + case 2: + delete(); + break; + case 3: + display(); + break; + case 4: + exit(0); + break; + default: + printf("Enter valid choice??\n"); + } + } +} +void insert() +{ + int item; + printf("Enter the element\n"); + scanf("%d", &item); + if ((rear + 1) % maxsize == front) + { + printf("OVERFLOW"); + return; + } + else if (front == -1 && rear == -1) + { + front = 0; + rear = 0; + } + else if (rear == maxsize - 1 && front != 0) + { + rear = 0; + } + else + { + rear = (rear + 1) % maxsize; + } + queue[rear] = item; + printf("Value inserted "); +} +void delete() +{ + int item; + if (front == -1 & rear == -1) + { + printf("UNDERFLOW\n"); + return; + } + else if (front == rear) + { + front = -1; + rear = -1; + } + else if (front == maxsize - 1) + { + front = 0; + } + else + front = front + 1; +} + +void display() +{ + int i; + if (front == -1) + printf("Circular Queue is Empty!!!\n"); + else + { + i = front; + printf("Circular Queue Elements are : \n"); + if (front <= rear) { + while (i <= rear) + printf("%d %d %d\n", queue[i++], front, rear); + } + else { + while (i <= maxsize - 1) + printf("%d %d %d\n", queue[i++], front, rear); + i = 0; + while (i <= rear) + printf("%d %d %d\n", queue[i++], front, rear); + } + } +} +``` +### 循环链表实现 diff --git a/数据结构/4.2 优先队列.md b/数据结构/4.2 优先队列.md new file mode 100644 index 00000000..843d0b50 --- /dev/null +++ b/数据结构/4.2 优先队列.md @@ -0,0 +1,31 @@ +# 优先队列 + + +## 1 优先队列简介 + + + + +## 3 优先队列的操作 + +### 基本操作 +* 创建 +* 遍历、搜索、查找(显示第一个元素) +* 插入 +* 删除 + + +## 3 优先队列的实现 + +### 数组实现 + +### 循环链表实现 + +### C++模板 + +``` +#include + +queue que; +``` + diff --git a/数据结构/5 散列表.md b/数据结构/5 散列表.md new file mode 100644 index 00000000..5ab18c01 --- /dev/null +++ b/数据结构/5 散列表.md @@ -0,0 +1,82 @@ +# 哈希表 + + +## 1 哈希表简介 + +### 哈希表的概念 +* `哈希表`是一种使用`哈希函数`组织数据,以支持快速插入和搜索的数据结构。 +* 通过选择合适的哈希函数,哈希表可以在插入和搜索方面实现`出色的性能`。 + +### 哈希表的分类 +* 有两种不同类型的哈希表:哈希集合和哈希映射。 + - `哈希集合`是`集合set`数据结构的实现之一,用于存储`非重复值`。 + - `哈希映射`是`映射map` 数据结构的实现之一,用于存储`(key, value)`键值对。 + + + +## 2 哈希表的原理 + +### 原理说明 +* 哈希表的关键思想是使用哈希函数将键映射到存储桶。更确切地说, + 1. 当我们插入一个新的键时,哈希函数将决定该键应该分配到哪个桶中,并将该键存储在相应的桶中; + 2. 当我们想要搜索一个键时,哈希表将使用相同的哈希函数来查找对应的桶,并只在特定的桶中进行搜索。 + +### 哈希函数示例 + +![](2021-03-13-12-51-08.png) + +* 在示例中,我们使用 y = x % 5 作为哈希函数。让我们使用这个例子来完成插入和搜索策略: + 1. 插入:我们通过哈希函数解析键,将它们映射到相应的桶中。 + - 例如,1987 分配给桶 2,而 24 分配给桶 4。 + 2. 搜索:我们通过相同的哈希函数解析键,并仅在特定存储桶中搜索。 + - 如果我们搜索 1987,我们将使用相同的哈希函数将 1987 映射到 2。因此我们在桶 2 中搜索,我们在那个桶中成功找到了 1987。 + - 例如,如果我们搜索 23,将映射 23 到 3,并在桶 3 中搜索。我们发现 23 不在桶 3 中,这意味着 23 不在哈希表中。 + +### 哈希表的关键——哈希函数 + +* 哈希函数是哈希表中最重要的组件,该哈希表用于将键映射到特定的桶。在上一节的示例中,我们使用 `y = x % 5` 作为散列函数,其中 `x` 是键值,`y` 是分配的桶的索引。 + +* 哈希函数将取决于`键值的范围`和`桶的数量。` + +* 哈希函数的示例: +![](2021-03-13-12-53-35.png) + +* 哈希函数的设计是一个开放的问题。其思想是尽可能将键分配到桶中,理想情况下,完美的哈希函数将是键和桶之间的一对一映射。然而,在大多数情况下,哈希函数并不完美,它需要在桶的数量和桶的容量之间进行权衡。 + +### 哈希表的关键——冲突解决 + +* 理想情况下,如果我们的哈希函数是完美的一对一映射,我们将不需要处理冲突。不幸的是,在大多数情况下,冲突几乎是不可避免的。例如,在我们之前的哈希函数(_y = x % 5_)中,1987 和 2 都分配给了桶 2,这是一个`冲突`。 + +* 冲突解决算法应该解决以下几个问题: + 1. 如何组织在同一个桶中的值? + 2. 如果为同一个桶分配了太多的值,该怎么办? + 3. 如何在特定的桶中搜索目标值? + +* 根据我们的哈希函数,这些问题与`桶的容量`和可能映射到`同一个桶`的`键的数目`有关。 + +* 让我们假设存储最大键数的桶有 `N` 个键。如果 N是常数且很小,我们可以简单地使用一个数组将键存储在同一个桶中。如果 N 是可变的或很大,我们可能需要使用`高度平衡的二叉树`来代替。 + + +## 哈希表的实现 + +### 自己设计哈希函数 + + +### C++STL中的实现 + +* 哈希集合 + +``` +#include +set s; +multiset ms; +unordered_multiset ums; +``` +* 哈希映射 + +``` +#include +map m; +multimap mm; +unordered_multimap umm; +``` \ No newline at end of file diff --git a/数据结构/6 树.md b/数据结构/6 树.md new file mode 100644 index 00000000..8f5de4e9 --- /dev/null +++ b/数据结构/6 树.md @@ -0,0 +1,93 @@ +# 树 + +> 树 + +## 0 简介 + +### 定义 +**树**是一种抽象数据类型或是实现这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合。它是由 n(n>0)个有限节点组成一个具有层次关系的集合。 + +* 树是一种递归数据结构,包含一个或多个数据节点的集合,其中一个节点被指定为树的根,而其余节点被称为根的子节点。 +* 除根节点之外的节点被划分为非空集,其中每个节点将被称为子树。 +* 树的节点要么保持它们之间的父子关系,要么它们是姐妹节点。 +* 在通用树中,一个节点可以具有任意数量的子节点,但它只能有一个父节点。 + +![](树.png) +### 特点 + +- 每个节点都只有有限个子节点或无子节点。 +- 树有且仅有一个根节点。 +- 根节点没有父节点;非根节点有且仅有一个父节点。 +- 每个非根节点可以分为多个不相交的子树。 +- 树里面没有环路。 + +### 术语 +- **祖先节点**: 节点的祖先是从根到该节点的路径上的任何前节点。根节点没有祖先节点。 +- **父节点**:若一个节点含有子节点,则这个节点称为其子节点的父节点; +- **子节点**:一个节点含有的子树的根节点称为该节点的子节点; +- **根节点**: 根节点是树层次结构中的最顶层节点。 换句话说,根节点是没有任何父节点的节点. +- **叶子节点**或**终端节点**:度为零的节点; +- **兄弟节点**:具有相同父节点的节点互称为兄弟节点; +- **非终端节点**或**分支节点**:度不为零的节点; +- **子孙**:以某节点为根的子树中任一节点都称为该节点的子孙。 + + +- **节点的度**:一个节点含有的子树的个数称为该节点的度; +- **树的度**:一棵树中,最大的节点度称为树的度; +- 节点的**层次**:从根开始定义起,根为第 1 层,根的子节点为第 2 层,以此类推; +- **深度**:对于任意节点 n,n 的深度为从根到 n 的唯一路径长,**根节点的深度为0**; +- **高度**:对于任意节点 n,n 的高度为从 n 到一片树叶的最长路径长,所有**叶节点的高度为0**; +- **森林**:由 m(m>=0)棵互不相交的树的集合称为森林; +- **路径**: 连续边的序列称为路径。 在上图所示的树中,节点E的路径为A→B→E。 +- **级别=水平**: 为树的每个节点分配一个级别编号,使得每个节点都存在于高于其父级的一个级别。**根节点级别0** +- **键**:节点总存储的值。 +- **索引**:该节点子节点的个数。 + + +### 性质 + +- 树中的节点数等于所有节点的度数加 1。 +- 度为 m 的树中第 i 层上至多有 $m^{i-1}$ 个节点($i ≥ 1$)。 +- 高度为 h 的 m 次树至多有 $(m^h-1)/(m-1)$ 个节点。 +- 具有 n 个节点的 m 次树的最小高度为 $\log_m{(n(m-1)+1)}$ 。 + + + +## 2 树的种类 +按节点有无顺序划分 +* **有序树**:树中任意节点的子节点之间有顺序关系,这种树称为有序树; +* **无序树**:树中任意节点的子节点之间没有顺序关系,这种树称为无序树,也称为自由树。 + + +按照结构划分 +* 一般树(General Tree)按层次顺序存储元素,其中顶级元素始终以0级作为根元素。 除根节点之外的所有节点都以级别数存在。 存在于同一级别的节点称为兄弟节点,而存在于不同级别的节点表现出它们之间的父子关系。 节点可以包含任意数量的子树。 每个节点包含3个子树的树称为三元树。 + +* 二叉树:每个节点最多含有两个子树的树称为二叉树; + - **严格二叉树**:对于一颗二叉树,假设其深度为 d(d>1)。除了第 d 层外,其它各层的节点数目均已达最大值,且第 d 层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树; + - 完全二叉树:所有叶节点都在最底层的完全二叉树; + - 平衡二叉树(AVL 树):当且仅当任何节点的两棵子树的高度差不大于 1 的二叉树; + - 搜多二叉树(二叉查找树):也称二叉搜索树、有序二叉树; + - B 树:一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多于两个子树。 + - B+树: +* 多叉树 + + +按应用场景划分 +* 霍夫曼树:最短的二叉树称为哈夫曼树或最优二叉树; +* 表达式树:用于评估简单的算术表达式。表达式树基本上是二叉树,其中内部节点由运算符表示,而叶节点由操作数表示。表达式树被广泛用于解决代数表达式,如(a + b)*(a-b) +![](2021-03-12-22-05-59.png) +* 竞赛树:用于记录两名比赛者之间每轮比赛的胜者。 比赛树也可以称为选择树或获胜者树。 外部节点表示正在播放匹配的比赛者,而内部节点表示所播放的匹配的胜者。 在最顶层,比赛的获胜者作为树的根节点出现。 +![](2021-03-12-22-07-20.png) + +## 3 树的实现 +### 数据实现 +* 使用结构体和指针实现树 +```C++ +struct treenode +{ + int root; + struct treenode *father;//父节点 + struct treenode *son;//子节点 + struct treenode *next;//兄弟节点 +} +``` \ No newline at end of file diff --git a/数据结构/6.1 二叉树.md b/数据结构/6.1 二叉树.md new file mode 100644 index 00000000..957a2ede --- /dev/null +++ b/数据结构/6.1 二叉树.md @@ -0,0 +1,124 @@ +# 二叉树 +> 树>二叉树 + +## 1 二叉树简介 +### 概念 + +* 二叉树是一种特殊类型的通用树,它的每个节点最多可以有两个子节点。 二叉树通常被划分为三个不相交的子集。 + * 节点的根 + * 左二叉树 + * 右二叉树 + +![](2021-03-12-22-10-38.png) + +### 性质 + +1. 二叉树第 i 层上的结点数目最多为 **2i-1** (i≥1)。 +2. 深度为 k 的二叉树至多有 **2k-1** 个结点(k≥1)。 +3. 包含 n 个结点的二叉树的高度至少为 **log2(n+1)**。 +4. 在任意一棵二叉树中,若终端结点的个数为 n0,度为 2 的结点数为 n2,则 n0=n2+1。 + +## 2 二叉树分类 + +### 严格二叉树 + +* 在严格二叉树中,每个非叶节点包含非空的左和右子树。 换句话说,每个非叶节点的等级将始终为2。具有n个叶子的严格二叉树将具有(2n-1)个节点。 + +![](2021-03-12-22-12-08.png) + +### 完全二叉树 + +* 如果所有叶子都位于相同的水平d,则称二元树是完全二叉树。完全二叉树是二叉树,在0级和d级之间的每个级别包含正好$2 ^d$个节点。 具有深度d的完全二叉树中的节点总数是$2^{(d+1)}-1$,其中叶节点是$2^d&,非叶节点是$2^d-1$。 + +![](2021-03-12-22-12-52.png) + + +## 3 二叉树的操作——遍历搜索 + +### 遍历搜索方法 + +* 深度优先搜索(DFS) + + * 在这个策略中,我们采用 深度 作为优先级,以便从跟开始一直到达某个确定的叶子,然后再返回到达另一个分支。深度优先搜索策略又可以根据根节点、左孩子和右孩子的相对顺序被细分为**先序遍历**,**中序遍历**和**后序遍历**。 + + +* 广度优先搜索(BFS) + + * 我们按照高度顺序一层一层的访问整棵树,高层次的节点将会比低层次的节点先被访问到。又称为**层序遍历**。 + + +| 遍历 | 描述 | +|----------|----------------------------------------| +| 前序遍历 | 首先遍历根,然后分别遍历左子树和右子树。 该过程将递归地应用于树的每个子树。 | +| 中序遍历 | 首先遍历左子树,然后分别遍历根和右子树。 该过程将递归地应用于树的每个子树。 | +| 后序遍历 | 遍历左子树,然后分别遍历右子树和根。 该过程将递归地应用于树的每个子树。 | +|层序遍历|按照层次的方法,依次遍历每一层的节点,一般使用队列实现| + + +### 前序遍历 +``` +``` +### 中序遍历 +``` +``` +### 后序遍历 +``` +``` +### 层序遍历 +``` +``` +## 4 二叉树实现 + +### 链表实现 + +* 二叉树以链表的形式存储在存储器中,其节点的数量存储在非连续的存储器位置并通过像树一样继承父子关系而链接在一起。 每个节点包含三个部分:指向左节点的指针,数据元素和指向右节点的指针。 每个二叉树都有一个根指针,指向二叉树的根节点。 在空二进制树中,根指针将指向null。 +![](2021-03-12-22-16-50.png) + +``` +``` +### 数组实现 + +* 这是存储树元素的最简单的内存分配技术,但它是一种效率低下的技术,因为它需要大量空间来存储树元素。 下图显示了二叉树及其内存分配。 +![](2021-03-12-22-17-33.png) + +* 在这种表示方式中,数组用于存储树元素。 数组的大小将等于树中存在的节点数。 树的根节点将出现在数组的第一个索引处。 如果节点存储在第i个索引处,则其左右子节点将存储在2i和2i + 1位置。 如果数组的第一个索引,即tree[1]为0,则表示树为空。 +``` +``` + + +## 5 二叉树相关的问题 + + +- [二叉树的前序遍历](https://leetcode-cn.com/problems/binary-tree-preorder-traversal) +- [二叉树的中序遍历](https://leetcode-cn.com/problems/binary-tree-inorder-traversal) +- [二叉树的后序遍历](https://leetcode-cn.com/problems/binary-tree-postorder-traversal) +- [二叉树的层序遍历](https://leetcode-cn.com/problems/binary-tree-level-order-traversal) + +#### 二叉树递归 + +- [二叉树的最大深度](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree) +- [对称二叉树](https://leetcode-cn.com/problems/symmetric-tree) +- [路径总和](https://leetcode-cn.com/problems/path-sum) + +#### 其他 + +- [ ] [maximum-depth-of-binary-tree](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) +- [ ] [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) +- [ ] [binary-tree-maximum-path-sum](https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/) +- [ ] [lowest-common-ancestor-of-a-binary-tree](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/) +- [ ] [binary-tree-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) +- [ ] [binary-tree-level-order-traversal-ii](https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/) +- [ ] [binary-tree-zigzag-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/) +- [ ] [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) +- [ ] [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) + +### 二叉搜索树经典题 + +- [ ] [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) +- [ ] [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) +- [ ] [delete-node-in-a-bst](https://leetcode-cn.com/problems/delete-node-in-a-bst/) +- [ ] [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) + +## 参考资料 + +- [https://zh.wikipedia.org/wiki/树\_(数据结构)]() diff --git a/数据结构/6.2 二叉搜索树.md b/数据结构/6.2 二叉搜索树.md new file mode 100644 index 00000000..f6e632cc --- /dev/null +++ b/数据结构/6.2 二叉搜索树.md @@ -0,0 +1,169 @@ +# 二叉搜索树 +> 树>二叉树>二叉搜索树 + +## 1 简介 + +### 概念 +* 二叉搜索树是二叉树,其节点按特定顺序排列。也称为称为有序二叉树。 + * 左子树中所有节点的值小于根的值。 + * 右子树中所有节点的值大于或等于根的值。 + * 此规则将递归地应用于根的所有左子树和右子树 + +![](2021-03-12-22-46-59.png) + +### 优点 + +* 在搜索过程中,它会在每一步删除半个子树。 +* 在二叉搜索树中搜索元素需要o(log2n)时间。 +* 在最坏的情况下,搜索元素所花费的时间是0(n)。 +* 与数组和链表中的操作相比,它还加快了插入和删除操作。 + +## 2 基本操作 +### 基本操作 + +* 创建 +* 遍历(同上) +* 插入 +* 删除 + + +### 插入 +* 使用下列元素创建二叉树的过程 +``` +43, 10, 79, 90, 12, 54, 11, 9, 50 +``` + +![](2021-03-12-22-48-28.png) + +### 删除 + + +## 3 二叉搜索树实现 +```C++ +#include +#include +using namespace std; +struct Node { + int data; + Node *left; + Node *right; +}; +Node* create(int item) +{ + Node* node = new Node; + node->data = item; + node->left = node->right = NULL; + return node; +} + +void inorder(Node *root) +{ + if (root == NULL) + return; + + inorder(root->left); + cout<< root->data << " "; + inorder(root->right); +} +Node* findMinimum(Node* cur) +{ + while(cur->left != NULL) { + cur = cur->left; + } + return cur; +} +Node* insertion(Node* root, int item) +{ + if (root == NULL) + return create(item); + if (item < root->data) + root->left = insertion(root->left, item); + else + root->right = insertion(root->right, item); + + return root; +} + +void search(Node* &cur, int item, Node* &parent) +{ + while (cur != NULL && cur->data != item) + { + parent = cur; + + if (item < cur->data) + cur = cur->left; + else + cur = cur->right; + } +} + +void deletion(Node*& root, int item) +{ + Node* parent = NULL; + Node* cur = root; + + search(cur, item, parent); + if (cur == NULL) + return; + + if (cur->left == NULL && cur->right == NULL) + { + if (cur != root) + { + if (parent->left == cur) + parent->left = NULL; + else + parent->right = NULL; + } + else + root = NULL; + + free(cur); + } + else if (cur->left && cur->right) + { + Node* succ = findMinimum(cur- >right); + + int val = succ->data; + + deletion(root, succ->data); + + cur->data = val; + } + + else + { + Node* child = (cur->left)? Cur- >left: cur->right; + + if (cur != root) + { + if (cur == parent->left) + parent->left = child; + else + parent->right = child; + } + + else + root = child; + free(cur); + } +} + +int main() +{ + Node* root = NULL; + int keys[8]; + for(int i=0;i<8;i++) + { + cout << "Enter value to be inserted"; + cin>>keys[i]; + root = insertion(root, keys[i]); + } + + inorder(root); + cout<<"\n"; + deletion(root, 10); + inorder(root); + return 0; +} +``` \ No newline at end of file diff --git a/数据结构/6.3 平衡二叉树.md b/数据结构/6.3 平衡二叉树.md new file mode 100644 index 00000000..8afd25c4 --- /dev/null +++ b/数据结构/6.3 平衡二叉树.md @@ -0,0 +1,220 @@ +# 平衡二叉树 +> 树>二叉树>二叉搜索树>二叉平衡树 + +> 参考文献 +> * [AVL](https://zhuanlan.zhihu.com/p/56066942) +## 1 简介 + +### 概念 + +* 所有结点的平衡因子的绝对值都不超过1,即平衡因子只能是1,0,-1三个值。 +``` +平衡系数=左子树的高度 - 右子树的高度 +``` + +* 如果任何节点的平衡因子为1,则意味着左子树比右子树高一级。 +* 如果任何节点的平衡因子为0,则意味着左子树和右子树包含相等的高度。 +* 如果任何节点的平衡因子是-1,则意味着左子树比右子树低一级。 + +![](2021-03-12-22-53-43.png) + + +### 复杂性 + +| 算法 | 平均情况 | 最坏情况 | +|----|----------|----------| +| 空间 | o(n) | o(n) | +| 搜索 | o(log n) | o(log n) | +| 插入 | o(log n) | o(log n) | +| 删除 | o(log n) | o(log n) | + +### 优势 + +* AVL树能防止二叉树偏斜,控制二叉搜索树的高度。高度为h的二叉搜索树中的所有操作所花费的时间是O(h)。 +* 如果普通二叉搜索树变得偏斜(即最坏的情况),它可以扩展到O(n)。 +* 通过将该高度限制为log n,AVL树将每个操作的上限强加为O(log n),其中n是节点的数量 + +## 2 操作 + +### 基本操作 + +* 创建 +* 遍历(同上) +* 插入 +* 删除 + + +### 插入 +* AVL树中的插入的执行方式与在二叉搜索树中执行的方式相同。 +* 它可能会导致违反AVL树属性,因此树可能需要平衡。可以通过应用旋转来平衡树。 + +### 删除 +* 删除也可以按照在二叉搜索树中执行的相同方式执行。 +* 删除也可能会扰乱树的平衡,因此,使用各种类型的旋转来重新平衡树。 +* 删除节点的步骤如下: + 1. 以前三种情况为基础尝试删除节点,并将访问节点入栈。 + 2. 如果尝试删除成功,则依次检查栈顶节点的平衡状态,遇到非平衡节点,即进行旋转平衡,直到栈空。 + 3. 如果尝试删除失败,证明是第四种情况。这时先找到被删除节点的右子树最小节点并删除它,将访问节点继续入栈。 + 4. 再依次检查栈顶节点的平衡状态和修正直到栈空。 +## 3 操作——平衡 +### 左旋 +> 为了使树平衡,使用的手段有 : 左旋和右旋。右旋, 即是顺时针旋转。左旋,即是逆时针旋转。 + +* 左旋。加入新节点 99 后,节点 66 的左子树高度为 1,右子树高度为 3,此时平衡因子为 -2。为保证树的平衡,此时需要对节点 66 做出旋转,因为右子树高度高于左子树,对节点进行左旋操作, + 1. 节点的右孩子替代此节点位置 + 2. 右孩子的左子树变为该节点的右子树 + 3. 节点本身变为右孩子的左子树 + +![](2021-03-13-14-06-44.png) +![](2021-03-13-14-09-24.png) + +### 右旋 + +* 右旋。 + 1. 节点的左孩子代表此节点 + 2. 节点的左孩子的右子树变为节点的左子树 + 3. 将此节点作为左孩子节点的右子树。 +![](2021-03-13-14-10-25.png) +![](2021-03-13-14-10-40.png) + + +### 插入过程的四种旋转 + +> 删除与插入节点的四种旋转方式类似 + +| 插入方式 | 描述 | 旋转方式 | +| -------- | --- | ------------ | + | LL | 在 A 的左子树的根节点的左子树上插入节点而破坏平衡 | 右旋转 | + | RR | 在 A 的右子树的根节点的右子树上插入节点而破坏平衡 | 左旋转 | + | LR | 在A的左子树的根节点的右子树上插入节点而破坏平衡 | 先左旋后右旋 | + | RL | 在 A 的右子树的根节点的左子树上插入节点而破坏平衡 | 先右旋后左旋 | + +### LL单次右旋 +* 由于在**A的左子树的根结点的左子树**上插入结点F(LL),需进行一次的向右的顺时针旋转操作 +![](2021-03-13-14-25-55.png) +```C++ +//LL型调整函数 +//返回:新父节点 +Tree LL_rotate(Tree node){ + //node为离操作结点最近的失衡的结点 + Tree parent=NULL,son; + //获取失衡结点的父节点 + parent=node->parent; + //获取失衡结点的左孩子 + son=node->lchild; + //设置son结点右孩子的父指针 + if (son->rchild!=NULL) son->rchild->parent=node; + //失衡结点的左孩子变更为son的右孩子 + node->lchild=son->rchild; + //更新失衡结点的高度信息 + update_depth(node); + //失衡结点变成son的右孩子 + son->rchild=node; + //设置son的父结点为原失衡结点的父结点 + son->parent=parent; + //如果失衡结点不是根结点,则开始更新父节点 + if (parent!=NULL){ + //如果父节点的左孩子是失衡结点,指向现在更新后的新孩子son + if (parent->lchild==node){ + parent->lchild=son; + }else{ + //父节点的右孩子是失衡结点 + parent->rchild=son; + } + } + //设置失衡结点的父亲 + node->parent=son; + //更新son结点的高度信息 + update_depth(son); + return son; +} +``` +### RR单次左旋 +* 在 A 的右子树的根节点的右子树上插入节点而破坏平衡。需要进行一次左旋操作。 +![](2021-03-13-14-28-34.png) + +```C++ +//RR型调整函数 +//返回新父节点 +Tree RR_rotate(Tree node){ + //node为离操作结点最近的失衡的结点 + Tree parent=NULL,son; + //获取失衡结点的父节点 + parent=node->parent; + //获取失衡结点的右孩子 + son=node->rchild; + //设置son结点左孩子的父指针 + if (son->lchild!=NULL){ + son->lchild->parent=node; + } + //失衡结点的右孩子变更为son的左孩子 + node->rchild=son->lchild; + //更新失衡结点的高度信息 + update_depth(node); + //失衡结点变成son的左孩子 + son->lchild=node; + //设置son的父结点为原失衡结点的父结点 + son->parent=parent; + //如果失衡结点不是根结点,则开始更新父节点 + if (parent!=NULL){ + //如果父节点的左孩子是失衡结点,指向现在更新后的新孩子son + if (parent->lchild==node){ + parent->lchild=son; + }else{ + //父节点的右孩子是失衡结点 + parent->rchild=son; + } + } + //设置失衡结点的父亲 + node->parent=son; + //更新son结点的高度信息 + update_depth(son); + return son; +} +``` +### LR先左旋后右旋 +* 在A的左子树的根节点的右子树上插入节点F而破坏平衡。需要先进行子树的左旋再进行A节点的右旋。 +![](2021-03-13-14-29-50.png) +```C++ +//LR型,先左旋转,再右旋转 +//返回:新父节点 +Tree LR_rotate(Tree node){ + RR_rotate(node->lchild); + return LL_rotate(node); +} +``` +### RL先右旋后左旋 +* 在 A 的右子树的根节点的左子树上插入节点F而破坏平衡。需要先进行子树右旋,然后进行A节点的左旋。 +![](2021-03-13-14-30-35.png) +```C++ +//RL型,先右旋转,再左旋转 +//返回:新父节点 +Tree RL_rotate(Tree node){ + LL_rotate(node->rchild); + return RR_rotate(node); +} +``` +## 4 实现 +```C++ +typedef int ElementType; + +struct AVLNode{ + + int depth; //深度,这里计算每个结点的深度,通过深度的比较可得出是否平衡 + + Tree parent; //该结点的父节点 + + ElementType val; //结点值 + + Tree lchild; + + Tree rchild; + + AVLNode(int val=0) { + parent = NULL; + depth = 0; + lchild = rchild = NULL; + this->val=val; + } +}; +``` \ No newline at end of file diff --git a/数据结构/6.4 红黑树.md b/数据结构/6.4 红黑树.md new file mode 100644 index 00000000..d3c9ddfb --- /dev/null +++ b/数据结构/6.4 红黑树.md @@ -0,0 +1,140 @@ +# 红黑树 +> 树>二叉树>二叉搜索树>平衡二叉搜索树>红黑树(自平衡二叉搜索树) + +> 参考文献 +> * [红黑树](https://www.cnblogs.com/skywang12345/p/3245399.html#a1) +## 1 简介 + +### 定义 +1. 每个节点或者是黑色,或者是红色。 +2. 根节点是黑色。 +3. 每个叶子节点(NIL)是黑色。(这里叶子节点,是指为空NULL的叶子节点!) +4. 如果一个节点是红色的,则它的子节点必须是黑色的。 +5. 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。 + +### 优势 +* 主要是用它来存储有序的数据,它的时间复杂度是O(lgn),效率非常之高。 + +### 时间复杂度 +* 红黑树的时间复杂度为: O(lgn) +## 2 操作 +> 太过复杂,日后处理,根据参考文献。 +### 基础操作 + +* 创建 +* 遍历(同上) +* 插入 +* 删除 + +### 插入 + +* 第一步: 将红黑树当作一颗二叉查找树,将节点插入。 + * 红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。此外,无论是左旋还是右旋,若旋转之前这棵树是二叉查找树,旋转之后它一定还是二叉查找树。这也就意味着,任何的旋转和重新着色操作,都不会改变它仍然是一颗二叉查找树的事实。 + * 好吧?那接下来,我们就来想方设法的旋转以及重新着色,使这颗树重新成为红黑树! + +* 第二步:将插入的节点着色为"红色"。 + * 为什么着色成红色,而不是黑色呢?为什么呢? + * 将插入的节点着色为红色,不会违背"特性(5)"!少违背一条特性,就意味着我们需要处理的情况越少。接下来,就要努力的让这棵树满足其它性质即可;满足了的话,它就又是一颗红黑树了。o(∩∩)o...哈哈 + +* 第三步: 通过一系列的旋转或着色等操作,使之重新成为一颗红黑树。 + * 第二步中,将插入节点着色为"红色"之后,不会违背"特性(5)"。那它到底会违背哪些特性呢? + * 对于"特性(1)",显然不会违背了。因为我们已经将它涂成红色了。 + * 对于"特性(2)",显然也不会违背。在第一步中,我们是将红黑树当作二叉查找树,然后执行的插入操作。而根据二叉查找数的特点,插入操作不会改变根节点。所以,根节点仍然是黑色。 + * 对于"特性(3)",显然不会违背了。这里的叶子节点是指的空叶子节点,插入非空节点并不会对它们造成影响。 + * 对于"特性(4)",是有可能违背的! + * 那接下来,想办法使之"满足特性(4)",就可以将树重新构造成红黑树了。 + +* 使用伪代码了解流程 +``` +RB-INSERT(T, z) + y ← nil[T] // 新建节点“y”,将y设为空节点。 + x ← root[T] // 设“红黑树T”的根节点为“x” + while x ≠ nil[T] // 找出要插入的节点“z”在二叉树T中的位置“y” + do y ← x + if key[z] < key[x] + then x ← left[x] + else x ← right[x] + p[z] ← y // 设置 “z的父亲” 为 “y” + if y = nil[T] + then root[T] ← z // 情况1:若y是空节点,则将z设为根 + else if key[z] < key[y] + then left[y] ← z // 情况2:若“z所包含的值” < “y所包含的值”,则将z设为“y的左孩子” + else right[y] ← z // 情况3:(“z所包含的值” >= “y所包含的值”)将z设为“y的右孩子” + left[z] ← nil[T] // z的左孩子设为空 + right[z] ← nil[T] // z的右孩子设为空。至此,已经完成将“节点z插入到二叉树”中了。 + color[z] ← RED // 将z着色为“红色” + RB-INSERT-FIXUP(T, z) // 通过RB-INSERT-FIXUP对红黑树的节点进行颜色修改以及旋转,让树T仍然是一颗红黑树 +``` + +* 使用改善伪代码 +``` +RB-INSERT-FIXUP(T, z) + while color[p[z]] = RED // 若“当前节点(z)的父节点是红色”,则进行以下处理。 + do if p[z] = left[p[p[z]]] // 若“z的父节点”是“z的祖父节点的左孩子”,则进行以下处理。 + then y ← right[p[p[z]]] // 将y设置为“z的叔叔节点(z的祖父节点的右孩子)” + if color[y] = RED // Case 1条件:叔叔是红色 + then color[p[z]] ← BLACK ▹ Case 1 // (01) 将“父节点”设为黑色。 + color[y] ← BLACK ▹ Case 1 // (02) 将“叔叔节点”设为黑色。 + color[p[p[z]]] ← RED ▹ Case 1 // (03) 将“祖父节点”设为“红色”。 + z ← p[p[z]] ▹ Case 1 // (04) 将“祖父节点”设为“当前节点”(红色节点) + else if z = right[p[z]] // Case 2条件:叔叔是黑色,且当前节点是右孩子 + then z ← p[z] ▹ Case 2 // (01) 将“父节点”作为“新的当前节点”。 + LEFT-ROTATE(T, z) ▹ Case 2 // (02) 以“新的当前节点”为支点进行左旋。 + color[p[z]] ← BLACK ▹ Case 3 // Case 3条件:叔叔是黑色,且当前节点是左孩子。(01) 将“父节点”设为“黑色”。 + color[p[p[z]]] ← RED ▹ Case 3 // (02) 将“祖父节点”设为“红色”。 + RIGHT-ROTATE(T, p[p[z]]) ▹ Case 3 // (03) 以“祖父节点”为支点进行右旋。 + else (same as then clause with "right" and "left" exchanged) // 若“z的父节点”是“z的祖父节点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。 + color[root[T]] ← BLACK +``` +* 根据被插入节点的父节点的情况,可以将"当节点z被着色为红色节点,并插入二叉树"划分为三种情况来处理。 + * 情况说明:被插入的节点是根节点。处理方法:直接把此节点涂为黑色。 + * 情况说明:被插入的节点的父节点是黑色。处理方法:什么也不需要做。节点被插入后,仍然是红黑树。 + * 情况说明:被插入的节点的父节点是红色。处理方法:那么,该情况与红黑树的“特性(4)”相冲突。这种情况下,被插入节点是一定存在非空祖父节点的;进一步的讲,被插入节点也一定存在叔叔节点(即使叔叔节点为空,我们也视之为存在,空节点本身就是黑色节点)。理解这点之后,我们依据"叔叔节点的情况",将这种情况进一步划分为3种情况(Case)。 +### 删除 + + +## 3 旋转 + +### 左旋 +* 失去平衡,逆时针旋转 +![](2021-03-13-14-43-51.png) +``` +LEFT-ROTATE(T, x) + y ← right[x] // 前提:这里假设x的右孩子为y。下面开始正式操作 + right[x] ← left[y] // 将 “y的左孩子” 设为 “x的右孩子”,即 将β设为x的右孩子 + p[left[y]] ← x // 将 “x” 设为 “y的左孩子的父亲”,即 将β的父亲设为x + p[y] ← p[x] // 将 “x的父亲” 设为 “y的父亲” + if p[x] = nil[T] + then root[T] ← y // 情况1:如果 “x的父亲” 是空节点,则将y设为根节点 + else if x = left[p[x]] + then left[p[x]] ← y // 情况2:如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子” + else right[p[x]] ← y // 情况3:(x是它父节点的右孩子) 将y设为“x的父节点的右孩子” + left[y] ← x // 将 “x” 设为 “y的左孩子” + p[x] ← y // 将 “x的父节点” 设为 “y” +``` + +### 右旋 + +* 失去平衡,向右旋转 +![](2021-03-13-14-44-34.png) + +``` +RIGHT-ROTATE(T, y) + x ← left[y] // 前提:这里假设y的左孩子为x。下面开始正式操作 + left[y] ← right[x] // 将 “x的右孩子” 设为 “y的左孩子”,即 将β设为y的左孩子 + p[right[x]] ← y // 将 “y” 设为 “x的右孩子的父亲”,即 将β的父亲设为y + p[x] ← p[y] // 将 “y的父亲” 设为 “x的父亲” + if p[y] = nil[T] + then root[T] ← x // 情况1:如果 “y的父亲” 是空节点,则将x设为根节点 + else if y = right[p[y]] + then right[p[y]] ← x // 情况2:如果 y是它父节点的右孩子,则将x设为“y的父节点的左孩子” + else left[p[y]] ← x // 情况3:(y是它父节点的左孩子) 将x设为“y的父节点的左孩子” + right[x] ← y // 将 “y” 设为 “x的右孩子” + p[y] ← x // 将 “y的父节点” 设为 “x” +``` + +## 4 实现 + +``` + +``` \ No newline at end of file diff --git a/数据结构/6.5 B树.md b/数据结构/6.5 B树.md new file mode 100644 index 00000000..9e669490 --- /dev/null +++ b/数据结构/6.5 B树.md @@ -0,0 +1,96 @@ +# B树 + +## 1 简介 + +### 概念 + +* B 树又叫平衡多路查找树。一棵m阶的B树定义如下: + 1. B树中的每个节点最多包含m个子节点。最多包含m-1个键。 + 2. 除根节点和叶节点外,B树中的每个节点至少包含[m/2](向上取整)个子节点。 + 3. 根节点要么是空、要么是独自的根、要么必须至少有2个子节点。 + 4. 有k个子节点的节点必有k-1个键。每个键按顺序升序排序。 + 5. 所有叶节点必须处于同一层(水平)。 + + +* 4阶B树如下 + +![](2021-03-12-23-35-36.png) + + +### 应用 +* 大规模数据存储中,二叉树节点存储的元素数量是有限的(如果元素数量非常多的话,查找就退化成节点内部的线性查找了),这样导致二叉查找树结构由于树的深度过大而造成磁盘I/O读写过于频繁,进而导致查询效率低下 +* 如何减少树的深度,一个基本的想法就是:采用多叉树结构(由于树节点元素数量是有限的,自然该节点的子树数量也就是有限的)。 +* 多路查找树。根据平衡二叉树的启发,使用平衡多路查找树结构,也就是这篇文章所要阐述的第一个Btree。 + +### 分类 + +* B树 +* B+树 +* B*树 + +## 2 操作 + +### 基础操作 + +* 创建 +* 遍历和搜索 +* 插入 +* 删除 + + +### 创建 + + +### 搜索 +> B树中搜索类似于二叉搜索树中的搜索 +* 将数据项49与根节点78进行比较。 +* 因为49 <78因此,移动到其左子树。 +* 因为,40 <49 <56,遍历中间子树40。49> 45,向右移动。 +* 比较49。找到匹配,则返回。 + +> 在B树中搜索取决于树的高度。 搜索算法需要O(log n)时间来搜索B树中的任何元素。 + +![](2021-03-12-23-39-19.png) + +### 插入 + + +* 遍历B树以找到可插入节点的适当叶节点。 +* 如果叶节点包含少于m-1个键,则按递增顺序插入元素。 +* 否则,如果叶节点包含m-1个键,则按照以下步骤操作。 + * 按元素的递增顺序插入新元素。 + * 将节点从中间拆分为的两个节点。 + * 将中值元素推送到其父节点。 + * 如果父节点还包含m-1个键,则按照相同的步骤将其拆分。 + +> 实例 +* 将节点8插入到下图所示的5阶B树中。 +![](2021-03-12-23-47-41.png) +* 8将插入5的右侧,因此插入8。 +![](2021-03-12-23-47-46.png) +* 该节点现在包含5个键,大于(5 -1 = 4)个键。 因此,将节点从中间分开,即8,并将其推到其父节点,如下所示。(图有问题) +![](2021-03-12-23-47-52.png) + +### 删除 + +* 找到叶节点。 +* 如果叶节点中有多于m/2个键,则从节点中删除所需的键。 +* 如果叶节点不包含m/2个键,则通过从8个或左兄弟中获取元素来完成键。 + * 如果左侧兄弟包含多于m/2个元素,则将其最大元素推送到其父元素,并将插入元素向下移动到删除键的节点。 + * 如果右侧兄弟包含多于m/2个元素,则将其最小元素向上推送到父节点,并将插入元素向下移动到删除键的节点。 + * 如果兄弟节点都不包含多于m/2个元素,则通过连接两个叶节点和父节点的插入元素来创建新的叶节点。 + * 如果父节点的节点少于m/2,那么也应在父节点上应用上述过程。 +* 如果要删除的节点是内部节点,则将节点替换为其有序后继或前一个节点。 由于后继或前任将始终位于叶节点上,因此该过程将类似于从叶节点中删除节点。 + +> 实例 + +* 从下图所示的5阶B树中删除节点:53。 +![](2021-03-12-23-51-37.png) +* 元素49的右子节点中存在53,则删除它。 +![](2021-03-12-23-51-43.png) +* 现在,57是唯一留在节点中的元素,在5阶B树中必须存在的最小元素数是2。它小于左边和右边子树中的元素 因此,也不足以将其与父母的左兄弟和干预元素合并,即49。最终的B树如下所示。 +![](2021-03-12-23-51-51.png) + +### 实现 +``` +``` \ No newline at end of file diff --git a/数据结构/6.6 B+树.md b/数据结构/6.6 B+树.md new file mode 100644 index 00000000..9d96b116 --- /dev/null +++ b/数据结构/6.6 B+树.md @@ -0,0 +1,57 @@ +# B+树 +> 参考文献 +> * [B树B+树](https://www.cnblogs.com/lianzhilei/p/11250589.html) +## 1 简介 +### 概念 + +> 像是在建立额外的索引 + +1. 有m个子树的中间节点包含有m个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引; +2. 所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接。 (而B 树的叶子节点并没有包括全部需要查找的信息); +3. 所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。 (而B 树的非终节点也包含需要查找的有效信息); + +### 优点 + +* B+树的磁盘读写代价更低 + * B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了; + +* B+树查询效率更加稳定 +* 由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当; + +* B+树便于范围查询(最重要的原因,范围查找是数据库的常态) + * B树在提高了IO性能的同时并没有解决元素遍历的我效率低下的问题,正是为了解决这个问题,B+树应用而生。B+树只需要去遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作或者说效率太低;不懂可以看看这篇解读-》范围查找 + + +## 2 操作 +### 搜索 +### 插入 +* 第1步 :将新节点作为叶节点插入。 +* 第2步 :如果叶子没有所需空间,则拆分节点并将中间节点复制到下一个索引节点。 +* 第3步 :如果索引节点没有所需空间,则拆分节点并将中间元素复制到下一个索引节点 + +> 实例 + +* 将值195插入到下图所示的5阶B+树中。 +![](2021-03-12-23-58-07.png) +* 在190之后,将在右子树120中插入195。将其插入所需位置。 +![](2021-03-12-23-58-14.png) +* 该节点包含大于最大的元素数量,即4,因此将其拆分并将中间节点放置到父节点。 +![](2021-03-12-23-58-20.png) +* 现在,索引节点包含6个子节点和5个键,违反B+树属性,因此需要将其拆分,如下所示。将108提为根节点。60,78左子节点,120,190右子节点。 +![](2021-03-12-23-58-29.png) + +### 删除 + +* 第1步:从叶子中删除键和数据。 +* 第2步:如果叶节点包含少于最小数量的元素,则将节点与其兄弟节点合并,并删除它们之间的键。 +* 第3步:如果索引节点包含少于最小数量的元素,则将节点与兄弟节点合并,并在它们之间向下移动键。 + +> 示例 +* 从下图所示的B+树中删除键为200。 +![](2021-03-13-00-03-01.png) +* 在195之后的190的右子树中存在200,删除它。 +![](2021-03-13-00-03-07.png) +* 使用195,190,154和129合并两个节点。 +![](2021-03-13-00-03-13.png) +* 现在,元素120是节点中存在的违反B+树属性的单个元素。 因此,需要使用60,78,108和120来合并它。现在,B+树的高度将减1。 +![](2021-03-13-00-03-30.png) diff --git a/数据结构/6.8 堆树.md b/数据结构/6.8 堆树.md new file mode 100644 index 00000000..1bf7323f --- /dev/null +++ b/数据结构/6.8 堆树.md @@ -0,0 +1,113 @@ +# 堆 +> * 二叉搜索树、多路搜索树都是左小右大。堆树是上下大小不一样。 +> * 二叉搜索树是从根节点向叶子节点构造。进行插入。堆树是从叶子节点,向根节点进行构造。 + +> 参考文献 +> * [堆,但是有很严重的错误](https://blog.csdn.net/qq_34270874/article/details/113091364) + + +## 1 简介 + +1. 堆是一颗完全二叉树; +2. 堆中的某个结点的值总是大于等于(最大堆)或小于等于(最小堆)其孩子结点的值。 +3. 堆中每个结点的子树都是堆树 + +![](2021-03-13-15-23-14.png) + +![](2021-03-13-15-23-24.png) + +* 使用数组表示的堆树 +![](2021-03-13-15-31-06.png) + +### 应用 + +* 堆结构的一个常见应用是建立优先队列(Priority Queue)。 +![](2021-03-13-15-25-15.png) +* 普通树占用的内存空间比它们存储的数据要多。你必须为节点对象以及左/右子节点指针分配内存。堆仅仅使用一个数据来存储数组,且不使用指针。 + + +## 2 操作 +### 基本操作 +* 上浮 +* 下沉 +* 取顶 +* 创建:把一个乱序的数组变成堆结构的数组,时间复杂度为 $O(n)$ +* 插入:把一个数值放进已经是堆结构的数组中,并保持堆结构,时间复杂度为 $O(log N)$ +* 删除:从最大堆中取出最大值或从最小堆中取出最小值,并将剩余的数组保持堆结构,时间复杂度为 $O(log N)$。 +* 堆排序:借由 HEAPFY 建堆和 HEAPPOP 堆数组进行排序,时间复杂度为$O(N log N)$,空间复杂度为 $O(1)$。 + +### 上浮-向上调整 +> 最小堆为例 + +* 向上调整 是让调整的结点与其父亲结点进行比较。从**当前节点递归处理到根节点**。 + 1. 先设定倒数的第一个叶子节点为当前节点(通过下标获取,标记为cur),找出他的父亲节点,用parent来标记。 + 2. 比较parent和cur的值,如果cur比parent小,则不满足小堆的规则,需要进行交换。 + 3. 如果cur比parent大,满足小堆的规则,不需要交换,调整结束。 + 4. 处理完一个节点之后,从当前的parent出发,循环之前的过程。 + +* 向上调整算法 小堆 +* 向上调整算法 大堆 +* **上浮能够保证路径上最好的值,上浮到根节点。** + + +### 下沉-向下调整 +> 最小堆为例 + +* 向下调整 是让调整的结点与其孩子节点进行比较。从**当前节点递归处理到叶节点**。 + 1. 先设定根节点为当前节点(通过下标获取,标记为cur),比较左右子树的值,找出更小的值,用child来标记。 + 2. 比较child和cur的值,如果child比cur小,则不满足小堆的规则,需要进行交换。 + 3. 如果child比cur大,满足小堆的规则,不需要交换,调整结束。 + 4. 处理完一个节点之后,从当前的child出发,循环之前的过程 + +* 向下调整(小堆)示例: + +![](2021-03-13-15-43-40.png) + +* 向下调整(大堆)示例: + +![](2021-03-13-15-43-53.png) + +* **下沉不能保证路径上最坏的值下放到叶节点。** + + +### 创建 + +> 类似于冒泡的思想,但是是一中更快的冒泡。保证最后能够建立最好的堆。 + +* 从根节点向叶节点开始,从后往前开始。进行多次上浮操作。 +* 从叶节点向根节点开始,从后往前开始。进行多次下沉操作。 + + +### 插入(堆尾的数据) + +* 将数据插入到数组最后,再进行向上调整。 + +![](2021-03-13-16-12-10.png) + + +### 删除(堆顶的数据) + +* 删除堆是删除堆顶的数据。将堆顶的数据和最后一个数据交换,然后删除数组最后一个数据,再进行向下调整算法 + +![](2021-03-13-17-02-40.png) + +### 堆排序 +* 堆排序 + 1. 将待排序序列构造成一个大顶堆。 + 2. 此时,整个序列的最大值就是堆顶的根节点。 + 3. 将其与末尾元素进行交换,此时末尾就为最大值。 + 4. 然后将剩余n-1个元素重新构造成一个堆(从根节点开始进行一次向下调整),这样会得到n-1个元素的次小值。如此反复执行,便能得到一个有序序列了。 + +## 3 堆的实现 + +``` + +struct MaxHeap +{ + EType *heap; //存放数据的空间,下标从1开始存储数据,下标为0的作为工作空间,存储临时数据。 + int MaxSize; //MaxSize是存放数据元素空间的大小 + int HeapSize; //HeapSize是数据元素的个数 +}; +MaxHeap H; +``` + diff --git a/数据结构/6.9 特异树.md b/数据结构/6.9 特异树.md new file mode 100644 index 00000000..f4d7cd9f --- /dev/null +++ b/数据结构/6.9 特异树.md @@ -0,0 +1 @@ +(注:切勿简单的认为一棵m阶的B树是m叉树,虽然存在四叉树,八叉树,KD树,及vp/R树/R*树/R+树/X树/M树/线段树/希尔伯特R树/优先R树等空间划分树,但与B树完全不等同) \ No newline at end of file diff --git a/数据结构/7 图.md b/数据结构/7 图.md new file mode 100644 index 00000000..8b47c1d9 --- /dev/null +++ b/数据结构/7 图.md @@ -0,0 +1,138 @@ +# 图 + +## 1 简介 + +### 概念 + +* 图可以定义为用于顶点和边的组合。 图可以看作是循环树,图中顶点(节点)维持它们之间的任何复杂关系,而不是简单的父子关系。 +* 图G 可以定义为有序集G(V,E),其中V(G)表示顶点集,E(G)表示用于连接这些顶点的边集。 +* 图G(V,E)有5个顶点(A,B,C,D,E)和6个边((A,B),(B,C),(C,E),(E,D), (D,B),(D,A))如下图所示。 + +![](2021-03-13-00-07-04.png) + + +### 术语 + +- **阶(Order)** - 图 G 中点集 V 的大小称作图 G 的阶。 +- **子图(Sub-Graph)** - 当图 G'=(V',E')其中 V‘包含于 V,E’包含于 E,则 G'称作图 G=(V,E)的子图。每个图都是本身的子图。 +- 生成子图(Spanning Sub-Graph) - 指满足条件 V(G') = V(G)的 G 的子图 G'。 +- **导出子图**(Induced Subgraph) - 以图 G 的顶点集 V 的非空子集V1 为顶点集,以两端点均在 V1 中的全体边为边集的 G 的子图,称为 V1 导出的导出子图;以图 G 的边集 E 的非空子集 E1 为边集,以 E1 中边关联的顶点的全体为顶点集的 G 的子图,称为 E1 导出的导出子图。 +- **度(Degree)** - 一个顶点的度是指与该顶点相关联的边的条数,顶点 v 的度记作 d(v)。 +- **入度(In-degree)**和**出度(Out-degree)** - 对于有向图来说,一个顶点的度可细分为入度和出度。一个顶点的入度是指与其关联的各边之中,以其为终点的边数;出度则是相对的概念,指以该顶点为起点的边数。 +- **自环(Loop)** - 若一条边的两个顶点为同一顶点,则此边称作自环。 +- **路径**(Path) - 从 u 到 v 的一条路径是节点序列v0,e1,v1,e2,v2,...ek,vk,其中 ei 的顶点为 vi 及 vi - 1,k 称作路径的长度。如果它的起止顶点相同,该路径是“闭”的,反之,则称为“开”的。一条路径称为一简单路径(simple path),如果路径中除起始与终止顶点可以重合外,所有顶点两两不等。 +- **行迹(Trace)** - 如果路径 P(u,v)中的边各不相同,则该路径称为 u 到 v 的一条行迹。闭的行迹称作回路(Circuit)。 +- **轨迹(Track)** - 如果路径 P(u,v)中的顶点各不相同,则该路径称为 u 到 v 的一条轨迹。闭的轨迹称作圈(Cycle)。 +- **桥(Bridge)** - 若去掉一条边,便会使得整个图不连通,该边称为桥。 +* 相邻节点如果两个节点u和v通过边e连接,则节点u和v被称为邻居或相邻节点。 + +### 分类 + +- **有向图** - 如果给图的每条边规定一个方向,那么得到的图称为有向图。 +- **无向图** - 边没有方向的图称为无向图。 +- 连通图连通图是在V中的每两个顶点(u,v)之间存在一些路径的图。连通图中没有孤立的节点。 +- **完整图**完整图是每个节点与所有其他节点连接的图。 完整图包含n(n-1/2个边,其中n是图中节点的数量。 +- **权重图**在权重图中,为每个边分配一些数据,例如长度或重量。 边e的权重可以给定为w(e),其必须是指示穿过边缘的成本的正(+)值。 + +## 2 图的实现 + +### 邻接矩阵 +* 在顺序表示中,使用邻接矩阵来存储由顶点和边表示的映射。在邻接矩阵中,行和列由图顶点表示。 具有n个顶点的图将具有尺寸n×n。 +* 如果在Vi和Vj之间存在边缘,则无向图G的邻接矩阵表示中的项目Mij将为1。 + +* 无向图及其邻接矩阵表示如下图所示。 +![](2021-03-13-00-15-09.png) +* 有向图及其邻接矩阵表示如下图所示。 +![](2021-03-13-00-15-45.png) +* 权重有向图以及邻接矩阵表示如下图所示。 +![](2021-03-13-00-16-39.png) + + +### 邻接链表 + +* 邻接列表用于将图存储到计算机的内存中。考虑下图中显示的无向图并检查邻接列表表示。要为图中存在的每个节点维护邻接列表,邻接列表将节点值和指向下一个相邻节点的指针存储到相应节点。 如果遍历所有相邻节点,则将NULL存储在列表的最后一个节点的指针字段中。 邻接列表的长度之和等于无向图中存在的边数的两倍。 +![](2021-03-13-00-18-58.png) +* 有向图,并查看图的邻接列表表示。在有向图中,所有邻接列表的长度之和等于图中存在的边的数量。 +![](2021-03-13-00-23-57.png) +* 加权有向图,每个节点包含一个额外的字段,称为节点的权重。 有向图的邻接列表表示如下图所示。 +![](2021-03-13-00-25-14.png) + + +## 3 基本操作 +### 基本操作 + + +- 创建一个图结构 - CreateGraph(G) +- 检索给定顶点 - LocateVex(G,elem) +- 获取图中某个顶点 - GetVex(G,v) +- 为图中顶点赋值 - PutVex(G,v,value) +- 返回第一个邻接点 - FirstAdjVex(G,v) +- 返回下一个邻接点 - NextAdjVex(G,v,w) +- 插入一个顶点 - InsertVex(G,v) +- 删除一个顶点 - DeleteVex(G,v) +- 插入一条边 - InsertEdge(G,v,w) +- 删除一条边 - DeleteEdge(G,v,w) +- 遍历图 - Traverse(G,v) + +### 创建 + +### 搜索——广度优先搜索 + +* 广度优先搜索是一种图遍历算法,它从根节点开始遍历图并探索所有相邻节点。 然后,它选择最近的节点并浏览所有未探测的节点。 对于每个最近的节点,该算法遵循相同的过程,直到找到目标为止。 +* 下面给出了广度优先搜索的算法。算法从检查节点A及其所有邻居开始。在下一步中,搜索A的最近节点的邻居,并且在后续步骤中继续处理。 该算法探索所有节点的所有邻居,并确保每个节点只访问一次,并且没有访问任何节点两次。 + +* 算法 +``` +第1步:设置状态 = 1(就绪状态) + 对于G中的每个节点 +第2步:将起始节点A排队 + 并设置其状态 = 2 + (等待状态) +第3步:重复第4步和第5步,直到 + 队列是空的 +第4步:使节点N出列。处理它 + 并设置其状态 = 3 + (处理状态)。 +第5步:将所有邻居排队 + N处于就绪状态 + (其STATUS = 1)并设置它们状态 = 2 + (等待状态) + [循环结束] +第6步:退出 +``` +> 实例 +考虑下图中显示的图G,计算从节点A到节点E的最小路径p。给定每条边的长度为1。最小路径P可以通过应用广度优先搜索算法找到,该算法将从节点A开始并将以E结束。算法使用两个队列,即QUEUE1和QUEUE2。 QUEUE1保存要处理的所有节点,而QUEUE2保存从QUEUE1处理和删除的所有节点。 + +![](2021-03-13-00-29-43.png) + +``` +``` + + + +### 搜索——深度优先搜索 + +* 深度优先搜索(DFS)算法从图G的初始节点开始,然后越来越深,直到找到目标节点或没有子节点的节点。该算法然后从死角回溯到尚未完全未探索的最新节点。 +* 在DFS中使用的数据结构是堆栈。该过程类似于BFS算法。 在DFS中,通向未访问节点的边称为发现边,而通向已访问节点的边称为块边。 +* 算法 + +``` +第1步:为G中的每个节点设置STATUS = 1(就绪状态) +第2步:将起始节点A推入堆栈并设置其STATUS = 2(等待状态) +第3步:重复第4步和第5步,直到STACK为空 +第4步:弹出顶部节点N.处理它并设置其STATUS = 3(处理状态) +第5步:将处于就绪状态(其STATUS = 1)的N的所有邻居推入堆栈并设置它们 + STATUS = 2(等待状态) + [循环结束] +第6步:退出 +``` +> 实例 + +考虑图G及其邻接列表,如下图所示。 通过使用深度优先搜索(DFS)算法计算从节点H开始打印图的所有节点的顺序。 + +![](2021-03-13-00-30-58.png) + +``` +``` + + diff --git a/数据结构/hash-search.md b/数据结构/hash-search.md new file mode 100644 index 00000000..09e78e83 --- /dev/null +++ b/数据结构/hash-search.md @@ -0,0 +1,232 @@ +# Hash 表的查找 + +## 要点 + +### 哈希表和哈希函数 + +在记录的存储位置和它的关键字之间是建立一个确定的对应关系(映射函数),使每个关键字和一个存储位置能**唯一对应**。这个映射函数称为**哈希函数**,根据这个原则建立的表称为**哈希表(Hash Table)**,也叫**散列表**。 + +以上描述,如果通过数学形式来描述就是: + +若查找关键字为 **key**,则其值存放在 **f(key)** 的存储位置上。由此,**不需比较便可直接取得所查记录**。 + +***注:哈希查找与线性表查找和树表查找最大的区别在于,不用数值比较。*** + +### 冲突 + +若 key1 ≠ key2 ,而 f(key1) = f(key2),这种情况称为**冲突(Collision)**。 + +根据哈希函数f(key)和处理冲突的方法将一组关键字映射到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“像”作为记录在表中的存储位置,这一映射过程称为**构造哈希表**。 + +构造哈希表这个场景就像汽车找停车位,如果车位被人占了,只能找空的地方停。 + +![img](http://upload-images.jianshu.io/upload_images/3101171-4f4e0c3def86f7bb.jpg "点击查看源网页") + +## 构造哈希表 + +由以上内容可知,哈希查找本身其实不费吹灰之力,问题的关键在于如何构造哈希表和处理冲突。 + +常见的构造哈希表的方法有 `5` 种: + +### 直接定址法 + +说白了,就是小学时学过的**一元一次方程**。 + +即 f(key) = a * key + b。其中,a和b 是常数。 + +### 数字分析法 + +假设关键字是R进制数(如十进制)。并且哈希表中**可能出现的关键字都是事先知道的**,则可选取关键字的若干数位组成哈希地址。 + +选取的原则是使得到的哈希地址尽量避免冲突,即所选数位上的数字尽可能是随机的。 + +### 平方取中法 + +取关键字平方后的中间几位为哈希地址。通常在选定哈希函数时不一定能知道关键字的全部情况,仅取其中的几位为地址不一定合适; + +而一个数平方后的中间几位数和数的每一位都相关, 由此得到的哈希地址随机性更大。取的位数由表长决定。 + +### 除留余数法 + +取关键字被某个**不大于哈希表表长** m 的数 p 除后所得的余数为哈希地址。 + +即 f(key) = key % p (p ≤ m) + +这是一种**最简单、最常用**的方法,它不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。 + +注意:p的选择很重要,如果选的不好,容易产生冲突。根据经验,**一般情况下可以选p为素数**。 + +### 随机数法 + +选择一个随机函数,取关键字的随机函数值为它的哈希地址,即 f(key) = random(key)。 + +通常,在关键字长度不等时采用此法构造哈希函数较为恰当。 + +## 解决冲突 + +设计合理的哈希函数可以减少冲突,但不能完全避免冲突。 + +所以需要有解决冲突的方法,常见有两类: + +### 开放定址法 + +如果两个数据元素的哈希值相同,则在哈希表中为后插入的数据元素另外选择一个表项。 +当程序查找哈希表时,如果没有在第一个对应的哈希表项中找到符合查找要求的数据元素,程序就会继续往后查找,直到找到一个符合查找要求的数据元素,或者遇到一个空的表项。 + +**示例** + +若要将一组关键字序列 {1, 9, 25, 11, 12, 35, 17, 29} 存放到哈希表中。 + +采用除留余数法构造哈希表;采用开放定址法处理冲突。 + +不妨设选取的p和m为13,由 f(key) = key % 13 可以得到下表。 + +![img](https://upload-images.jianshu.io/upload_images/3101171-06a789e7f9b31da6.png) + +需要注意的是,在上图中有两个关键字的探查次数为 2 ,其他都是1。 + +这个过程是这样的: + +a. 12 % 13 结果是12,而它的前面有个 25 ,25 % 13 也是12,存在冲突。 + +我们使用开放定址法 (12 + 1) % 13 = 0,没有冲突,完成。 + +b. 35 % 13 结果是 9,而它的前面有个 9,9 % 13也是 9,存在冲突。 + +我们使用开放定址法 (9 + 1) % 13 = 10,没有冲突,完成。 + +### 拉链法 + +将哈希值相同的数据元素存放在一个链表中,在查找哈希表的过程中,当查找到这个链表时,必须采用线性查找方法。 + +在这种方法中,哈希表中每个单元存放的不再是记录本身,而是相应同义词单链表的头指针。 + +**示例** + +如果对开放定址法示例中提到的序列使用拉链法,得到的结果如下图所示: + +![img](https://upload-images.jianshu.io/upload_images/3101171-c14e03882e8a0f3a.png) + +## 实现一个哈希表 + +假设要实现一个哈希表,要求 + +a. 哈希函数采用**除留余数法**,即 f(key) = key % p (p ≤ m) + +b. 解决冲突采用**开放定址法**,即 f2(key) = (f(key)+i) % size (p ≤ m) + +(1)定义哈希表的数据结构 + +```java +class HashTable { + public int key = 0; // 关键字 + public int data = 0; // 数值 + public int count = 0; // 探查次数 +} +``` + +(2)在哈希表中查找关键字key + +根据设定的哈希函数,计算哈希地址。如果出现地址冲突,则按设定的处理冲突的方法寻找下一个地址。 + +如此反复,直到不冲突为止(查找成功)或某个地址为空(查找失败)。 + +```java +/** + * 查找哈希表 + * 构造哈希表采用除留取余法,即f(key) = key mod p (p ≤ size) + * 解决冲突采用开放定址法,即f2(key) = (f(key) + i) mod p (1 ≤ i ≤ size-1) + * ha为哈希表,p为模,size为哈希表大小,key为要查找的关键字 + */ +public int searchHashTable(HashTable[] ha, int p, int size, int key) { + int addr = key % p; // 采用除留取余法找哈希地址 + + // 若发生冲突,用开放定址法找下一个哈希地址 + while (ha[addr].key != NULLKEY && ha[addr].key != key) { + addr = (addr + 1) % size; + } + + if (ha[addr].key == key) { + return addr; // 查找成功 + } else { + return FAILED; // 查找失败 + } +} +``` + +(3)删除关键字为key的记录 + +在采用开放定址法处理冲突的哈希表上执行删除操作,只能在被删记录上做删除标记,而不能真正删除记录。 + +找到要删除的记录,将关键字置为删除标记DELKEY。 + +```java +public int deleteHashTable(HashTable[] ha, int p, int size, int key) { + int addr = 0; + addr = searchHashTable(ha, p, size, key); + if (FAILED != addr) { // 找到记录 + ha[addr].key = DELKEY; // 将该位置的关键字置为DELKEY + return SUCCESS; + } else { + return NULLKEY; // 查找不到记录,直接返回NULLKEY + } +} +``` + +(4)插入关键字为key的记录 + +将待插入的关键字key插入哈希表 +先调用查找算法,若在表中找到待插入的关键字,则插入失败; +若在表中找到一个开放地址,则将待插入的结点插入到其中,则插入成功。 + +```java +public void insertHashTable(HashTable[] ha, int p, int size, int key) { + int i = 1; + int addr = 0; + addr = key % p; // 通过哈希函数获取哈希地址 + if (ha[addr].key == NULLKEY || ha[addr].key == DELKEY) { // 如果没有冲突,直接插入 + ha[addr].key = key; + ha[addr].count = 1; + } else { // 如果有冲突,使用开放定址法处理冲突 + do { + addr = (addr + 1) % size; // 寻找下一个哈希地址 + i++; + } while (ha[addr].key != NULLKEY && ha[addr].key != DELKEY); + + ha[addr].key = key; + ha[addr].count = i; + } +} +``` + +(5)建立哈希表 + +先将哈希表中各关键字清空,使其地址为开放的,然后调用插入算法将给定的关键字序列依次插入。 + +```java +public void insertHashTable(HashTable[] ha, int p, int size, int key) { + int i = 1; + int addr = 0; + addr = key % p; // 通过哈希函数获取哈希地址 + if (ha[addr].key == NULLKEY || ha[addr].key == DELKEY) { // 如果没有冲突,直接插入 + ha[addr].key = key; + ha[addr].count = 1; + } else { // 如果有冲突,使用开放定址法处理冲突 + do { + addr = (addr + 1) % size; // 寻找下一个哈希地址 + i++; + } while (ha[addr].key != NULLKEY && ha[addr].key != DELKEY); + + ha[addr].key = key; + ha[addr].count = i; + } +} +``` + +### 完整示例 + +[示例代码](https://github.com/dunwu/algorithm/blob/master/codes/src/main/java/io/github/dunwu/algorithm/search/HashDemo.java) + +## 资源 + +《数据结构习题与解析》(B级第3版) diff --git a/数据结构/linear-list-search.md b/数据结构/linear-list-search.md new file mode 100644 index 00000000..0eeb5473 --- /dev/null +++ b/数据结构/linear-list-search.md @@ -0,0 +1,307 @@ +# 线性表的查找 + +## 概念 + +### 什么是查找? + +**查找**是根据给定的某个值,在表中确定一个关键字的值等于给定值的记录或数据元素。 + +### 查找算法的分类 + +若在查找的同时对表记录做修改操作(如插入和删除),则相应的表称之为**动态查找表**; + +否则,称之为**静态查找表**。 + +此外,如果查找的全过程都在内存中进行,称之为**内查找**; + +反之,如果查找过程中需要访问外存,称之为**外查找**。 + +### 查找算法性能比较的标准 + +**——平均查找长度ASL(Average Search Length)** + +由于查找算法的主要运算是关键字的比较过程,所以通常把查找过程中对关键字需要执行的**平均比较长度**(也称为**平均比较次数**)作为衡量一个查找算法效率优劣的比较标准。 + +![img](https://upload-images.jianshu.io/upload_images/3101171-a38f84148d091364.gif?imageMogr2/auto-orient/strip) + +**选取查找算法的因素** + +(1) 使用什么数据存储结构(如线性表、树形表等)。 + +(2) 表中的次序,即对无序表还是有序表进行查找。 + +## 顺序查找 + +**要点** + +它是一种最简单的查找算法,效率也很低下。 + +**存储结构** + +没有存储结构要求,可以无序,也可以有序。 + +**基本思想** + +从数据结构线形表的**一端**开始,**顺序扫描**,**依次**将扫描到的结点关键字与给定值k相**比较**,若相等则表示查找成功; + +若扫描结束仍没有找到关键字等于k的结点,表示查找失败。 + +**核心代码** + +```java +public int orderSearch(int[] list, int length, int key) { + // 从前往后扫描list数组,如果有元素的值与key相等,直接返回其位置 + for (int i = 0; i < length; i++) { + if (key == list[i]) { + return i; + } + } + + // 如果扫描完,说明没有元素的值匹配key,返回-1,表示查找失败 + return -1; +} +``` + +**算法分析** + +顺序查找算法**最好的情况**是,第一个记录即匹配关键字,则需要比较 **1** 次; + +**最坏的情况**是,最后一个记录匹配关键字,则需要比较 **N** 次。 + +所以,顺序查找算法的平均查找长度为 + +ASL = (N + N-1 + ... + 2 + 1) / N = (N+1) / 2 + +顺序查找的**平均时间复杂度**为**O(N)**。 + +## 二分查找 + +> **二分查找针对的是一个有序的数据集合,查找思想有点类似分治思想。每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为 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/数据结构/skiplist.md b/数据结构/skiplist.md new file mode 100644 index 00000000..636e7ce5 --- /dev/null +++ b/数据结构/skiplist.md @@ -0,0 +1,13 @@ +# 跳表 + +> 经典实现:Redis 的 Sorted Set、JDK 的 ConcurrentSkipListMap 和 ConcurrentSkipListSet 都是基于跳表实现。 + +只需要对链表稍加改造,就可以支持类似“二分”的查找算法。我们把改造之后的数据结构叫作**跳表**(Skip list)。 + +跳表是一种各方面性能都比较优秀的**动态数据结构**,可以支持快速的插入、删除、查找操作,写起来也不复杂,甚至可以替代[红黑树](https://zh.wikipedia.org/wiki/红黑树)(Red-black tree)。 + +在一个单链表中查询某个数据的时间复杂度是 O(n)。 + +在一个具有多级索引的跳表中,第一级索引的结点个数大约就是 n/2,第二级索引的结点个数大约就是 n/4,第三级索引的结点个数大约就是 n/8,依次类推,也就是说,第 k 级索引的结点个数是第 k-1 级索引的结点个数的 1/2,那第 k 级索引结点的个数就是 $$n/(2^k)$$ + +## 参考资料 diff --git a/数据结构/sort.md b/数据结构/sort.md new file mode 100644 index 00000000..85d80a2b --- /dev/null +++ b/数据结构/sort.md @@ -0,0 +1,923 @@ +# 细说排序算法 + +> 📦 本文已归档到:「[blog](https://github.com/dunwu/blog/tree/master/source/_posts/algorithm)」 +> +> 🔁 本文中的示例代码已归档到:「[algorithm-tutorial](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java)」 + + + +- [冒泡排序](#冒泡排序) + - [要点](#要点) + - [算法思想](#算法思想) + - [算法分析](#算法分析) + - [示例代码](#示例代码) +- [快速排序](#快速排序) + - [要点](#要点-1) + - [算法思想](#算法思想-1) + - [算法分析](#算法分析-1) + - [示例代码](#示例代码-1) +- [插入排序](#插入排序) + - [要点](#要点-2) + - [算法思想](#算法思想-2) + - [算法分析](#算法分析-2) + - [示例代码](#示例代码-2) +- [希尔排序](#希尔排序) + - [要点](#要点-3) + - [算法思想](#算法思想-3) + - [算法分析](#算法分析-3) + - [示例代码](#示例代码-3) +- [简单选择排序](#简单选择排序) + - [要点](#要点-4) + - [算法思想](#算法思想-4) + - [算法分析](#算法分析-4) + - [示例代码](#示例代码-4) +- [堆排序](#堆排序) + - [要点](#要点-5) + - [算法思想](#算法思想-5) + - [算法分析](#算法分析-5) + - [示例代码](#示例代码-5) +- [归并排序](#归并排序) + - [要点](#要点-6) + - [算法思想](#算法思想-6) + - [算法分析](#算法分析-6) + - [示例代码](#示例代码-6) +- [基数排序](#基数排序) + - [要点](#要点-7) + - [算法分析](#算法分析-7) + - [示例代码](#示例代码-7) + + + +## 冒泡排序 + +### 要点 + +冒泡排序是一种交换排序。 + +什么是交换排序呢? + +> 交换排序:两两比较待排序的关键字,并交换不满足次序要求的那对数,直到整个表都满足次序要求为止。 + +### 算法思想 + +它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。 + +这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端,故名。 + +假设有一个大小为 N 的无序序列。冒泡排序就是要每趟排序过程中通过两两比较,找到第 i 个小(大)的元素,将其往上排。 + +![img](http://dunwu.test.upcdn.net/cs/algorithm/sort/bubble-sort.png!zp) + +以上图为例,演示一下冒泡排序的实际流程: + +假设有一个无序序列 { 4. 3. 1. 2, 5 } + +- 第一趟排序:通过两两比较,找到第一小的数值 1 ,将其放在序列的第一位。 +- 第二趟排序:通过两两比较,找到第二小的数值 2 ,将其放在序列的第二位。 +- 第三趟排序:通过两两比较,找到第三小的数值 3 ,将其放在序列的第三位。 + +至此,所有元素已经有序,排序结束。 + +要将以上流程转化为代码,我们需要像机器一样去思考,不然编译器可看不懂。 + +- 假设要对一个大小为 N 的无序序列进行升序排序(即从小到大)。 + - 每趟排序过程中需要通过比较找到第 i 个小的元素。 + - 所以,我们需要一个外部循环,从数组首端(下标 0) 开始,一直扫描到倒数第二个元素(即下标 N - 2) ,剩下最后一个元素,必然为最大。 +- 假设是第 i 趟排序,可知,前 i-1 个元素已经有序。现在要找第 i 个元素,只需从数组末端开始,扫描到第 i 个元素,将它们两两比较即可。 + - 所以,需要一个内部循环,从数组末端开始(下标 N - 1),扫描到 (下标 i + 1)。 + +**核心代码** + +```java +public void bubbleSort(int[] list) { + int temp = 0; // 用来交换的临时数 + + // 要遍历的次数 + for (int i = 0; i < list.length - 1; i++) { + // 从后向前依次的比较相邻两个数的大小,遍历一次后,把数组中第i小的数放在第i个位置上 + for (int j = list.length - 1; j > i; j--) { + // 比较相邻的元素,如果前面的数大于后面的数,则交换 + if (list[j - 1] > list[j]) { + temp = list[j - 1]; + list[j - 1] = list[j]; + list[j] = temp; + } + } + + System.out.format("第 %d 趟:\t", i); + printAll(list); + } +} +``` + +### 算法分析 + +**冒泡排序算法的性能** + +| 参数 | 结果 | +| ------------------ | -------- | +| 排序类别 | 交换排序 | +| 排序方法 | 冒泡排序 | +| 时间复杂度平均情况 | O(N2) | +| 时间复杂度最坏情况 | O(N3) | +| 时间复杂度最好情况 | O(N) | +| 空间复杂度 | O(1) | +| 稳定性 | 稳定 | +| 复杂性 | 简单 | + +#### 时间复杂度 + +若文件的初始状态是正序的,一趟扫描即可完成排序。所需的关键字比较次数 C 和记录移动次数 M 均达到最小值:Cmin = N - 1, Mmin = 0。所以,冒泡排序最好时间复杂度为 O(N)。 + +若初始文件是反序的,需要进行 N -1 趟排序。每趟排序要进行 N - i 次关键字的比较(1 ≤ i ≤ N - 1),且每次比较都必须移动记录三次来达到交换记录位置。在这种情况下,比较和移动次数均达到最大值: + +Cmax = N(N-1)/2 = O(N2) + +Mmax = 3N(N-1)/2 = O(N2) + +冒泡排序的最坏时间复杂度为 O(N2)。 + +因此,冒泡排序的平均时间复杂度为 O(N2)。 + +总结起来,其实就是一句话:当数据越接近正序时,冒泡排序性能越好。 + +#### 算法稳定性 + +冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。 + +所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。 + +#### 优化 + +对冒泡排序常见的改进方法是加入标志性变量 exchange,用于标志某一趟排序过程中是否有数据交换。 + +如果进行某一趟排序时并没有进行数据交换,则说明所有数据已经有序,可立即结束排序,避免不必要的比较过程。 + +**核心代码** + +```java +// 对 bubbleSort 的优化算法 +public void bubbleSort_2(int[] list) { + int temp = 0; // 用来交换的临时数 + boolean bChange = false; // 交换标志 + + // 要遍历的次数 + for (int i = 0; i < list.length - 1; i++) { + bChange = false; + // 从后向前依次的比较相邻两个数的大小,遍历一次后,把数组中第i小的数放在第i个位置上 + for (int j = list.length - 1; j > i; j--) { + // 比较相邻的元素,如果前面的数大于后面的数,则交换 + if (list[j - 1] > list[j]) { + temp = list[j - 1]; + list[j - 1] = list[j]; + list[j] = temp; + bChange = true; + } + } + + // 如果标志为false,说明本轮遍历没有交换,已经是有序数列,可以结束排序 + if (false == bChange) + break; + + System.out.format("第 %d 趟:\t", i); + printAll(list); + } +} +``` + +### 示例代码 + +[我的 Github 测试例](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java) + +样本包含:数组个数为奇数、偶数的情况;元素重复或不重复的情况。且样本均为随机样本,实测有效。 + +## 快速排序 + +### 要点 + +> 快速排序是一种交换排序。 + +快速排序由 C. A. R. Hoare 在 1962 年提出。 + +### 算法思想 + +它的基本思想是: + +通过一趟排序将要排序的数据分割成独立的两部分:分割点左边都是比它小的数,右边都是比它大的数。 + +然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。 + +详细的图解往往比大堆的文字更有说明力,所以直接上图: + +![img](http://dunwu.test.upcdn.net/cs/algorithm/sort/quick-sort.png!zp) + +上图中,演示了快速排序的处理过程: + +1. 初始状态为一组无序的数组:2、4、5、1、3。 +2. 经过以上操作步骤后,完成了第一次的排序,得到新的数组:1、2、5、4、3。 +3. 新的数组中,以 2 为分割点,左边都是比 2 小的数,右边都是比 2 大的数。 +4. 因为 2 已经在数组中找到了合适的位置,所以不用再动。 +5. 2 左边的数组只有一个元素 1,所以显然不用再排序,位置也被确定。(注:这种情况时,left 指针和 right 指针显然是重合的。因此在代码中,我们可以通过设置判定条件 left 必须小于 right,如果不满足,则不用排序了)。 +6. 而对于 2 右边的数组 5、4、3,设置 left 指向 5,right 指向 3,开始继续重复图中的一、二、三、四步骤,对新的数组进行排序。 + +**核心代码** + +```java +public int division(int[] list, int left, int right) { + // 以最左边的数(left)为基准 + int base = list[left]; + while (left < right) { + // 从序列右端开始,向左遍历,直到找到小于base的数 + while (left < right && list[right] >= base) + right--; + // 找到了比base小的元素,将这个元素放到最左边的位置 + list[left] = list[right]; + + // 从序列左端开始,向右遍历,直到找到大于base的数 + while (left < right && list[left] <= base) + left++; + // 找到了比base大的元素,将这个元素放到最右边的位置 + list[right] = list[left]; + } + + // 最后将base放到left位置。此时,left位置的左侧数值应该都比left小; + // 而left位置的右侧数值应该都比left大。 + list[left] = base; + return left; +} + +private void quickSort(int[] list, int left, int right) { + + // 左下标一定小于右下标,否则就越界了 + if (left < right) { + // 对数组进行分割,取出下次分割的基准标号 + int base = division(list, left, right); + + System.out.format("base = %d:\t", list[base]); + printPart(list, left, right); + + // 对“基准标号“左侧的一组数值进行递归的切割,以至于将这些数值完整的排序 + quickSort(list, left, base - 1); + + // 对“基准标号“右侧的一组数值进行递归的切割,以至于将这些数值完整的排序 + quickSort(list, base + 1, right); + } +} +``` + +### 算法分析 + +快速排序算法的性能 + +| 参数 | 结果 | +| ------------------ | --------- | +| 排序类别 | 交换排序 | +| 排序方法 | 快速排序 | +| 时间复杂度平均情况 | O(Nlog2N) | +| 时间复杂度最坏情况 | O(N2) | +| 时间复杂度最好情况 | O(Nlog2N) | +| 空间复杂度 | O(Nlog2N) | +| 稳定性 | 不稳定 | +| 复杂性 | 较复杂 | + +#### 时间复杂度 + +当数据有序时,以第一个关键字为基准分为两个子序列,前一个子序列为空,此时执行效率最差。 + +而当数据随机分布时,以第一个关键字为基准分为两个子序列,两个子序列的元素个数接近相等,此时执行效率最好。 + +所以,数据越随机分布时,快速排序性能越好;数据越接近有序,快速排序性能越差。 + +#### 空间复杂度 + +快速排序在每次分割的过程中,需要 1 个空间存储基准值。而快速排序的大概需要 Nlog2N 次的分割处理,所以占用空间也是 Nlog2N 个。 + +#### 算法稳定性 + +在快速排序中,相等元素可能会因为分区而交换顺序,所以它是不稳定的算法。 + +### 示例代码 + +[我的 Github 测试例](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java) + +样本包含:数组个数为奇数、偶数的情况;元素重复或不重复的情况。且样本均为随机样本,实测有效。 + +## 插入排序 + +### 要点 + +> 直接插入排序是一种最简单的**插入排序**。 +> +> **插入排序**:每一趟将一个待排序的记录,按照其关键字的大小插入到有序队列的合适位置里,知道全部插入完成。 + +### 算法思想 + +在讲解直接插入排序之前,先让我们脑补一下我们打牌的过程。 + +![img](http://dunwu.test.upcdn.net/cs/algorithm/sort/insert-sort.png!zp) + +- 先拿一张 5 在手里, +- 再摸到一张 4,比 5 小,插到 5 前面, +- 摸到一张 6,嗯,比 5 大,插到 5 后面, +- 摸到一张 8,比 6 大,插到 6 后面, +- 。。。 +- 最后一看,我靠,凑到的居然是同花顺,这下牛逼大了。 + +以上的过程,其实就是典型的**直接插入排序,每次将一个新数据插入到有序队列中的合适位置里**。 + +很简单吧,接下来,我们要将这个算法转化为编程语言。 + +假设有一组无序序列 R0, R1, ... , RN-1。 + +- 我们先将这个序列中下标为 0 的元素视为元素个数为 1 的有序序列。 +- 然后,我们要依次把 R1, R2, ... , RN-1 插入到这个有序序列中。所以,我们需要一个**外部循环**,从下标 1 扫描到 N-1 。 +- 接下来描述插入过程。假设这是要将 Ri 插入到前面有序的序列中。由前面所述,我们可知,插入 Ri 时,前 i-1 个数肯定已经是有序了。 + +所以我们需要将 Ri 和 R0 \~ Ri-1 进行比较,确定要插入的合适位置。这就需要一个**内部循环**,我们一般是从后往前比较,即从下标 i-1 开始向 0 进行扫描。 + +**核心代码** + +```java +public void insertSort(int[] list) { + // 打印第一个元素 + System.out.format("i = %d:\t", 0); + printPart(list, 0, 0); + + // 第1个数肯定是有序的,从第2个数开始遍历,依次插入有序序列 + for (int i = 1; i < list.length; i++) { + int j = 0; + int temp = list[i]; // 取出第i个数,和前i-1个数比较后,插入合适位置 + + // 因为前i-1个数都是从小到大的有序序列,所以只要当前比较的数(list[j])比temp大,就把这个数后移一位 + for (j = i - 1; j >= 0 && temp < list[j]; j--) { + list[j + 1] = list[j]; + } + list[j + 1] = temp; + + System.out.format("i = %d:\t", i); + printPart(list, 0, i); + } +} +``` + +### 算法分析 + +**直接插入排序的算法性能** + +| 参数 | 结果 | +| ------------------ | ------------ | +| 排序类别 | 插入排序 | +| 排序方法 | 直接插入排序 | +| 时间复杂度平均情况 | O(N2) | +| 时间复杂度最坏情况 | O(N2) | +| 时间复杂度最好情况 | O(N) | +| 空间复杂度 | O(1) | +| 稳定性 | 稳定 | +| 复杂性 | 简单 | + +#### 时间复杂度 + +当数据**正序**时,执行效率**最好**,每次插入都不用移动前面的元素,时间复杂度为 **O(N)**。 + +当数据**反序**时,执行效率**最差**,每次插入都要前面的元素后移,时间复杂度为 **O(N2)**。 + +所以,**数据越接近正序,直接插入排序的算法性能越好**。 + +#### 空间复杂度 + +由直接插入排序算法可知,我们在排序过程中,需要一个临时变量存储要插入的值,所以空间复杂度为 **1** 。 + +#### 算法稳定性 + +直接插入排序的过程中,不需要改变相等数值元素的位置,所以它是**稳定的**算法。 + +### 示例代码 + +[我的 Github 测试例](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java) + +样本包含:数组个数为奇数、偶数的情况;元素重复或不重复的情况。且样本均为随机样本,实测有效。 + +## 希尔排序 + +### 要点 + +> 希尔(Shell)排序又称为**缩小增量排序**,它是一种**插入排序**。它**是直接插入排序算法的一种威力加强版**。 + +该方法因 DL.Shell 于 1959 年提出而得名。 + +### 算法思想 + +希尔排序的**基本思想**是: + +把记录按**步长 gap** 分组,对每组记录采用**直接插入排序**方法进行排序。 +随着**步长逐渐减小**,所分成的组包含的记录越来越多,当步长的值减小到 **1** 时,整个数据合成为一组,构成一组有序记录,则完成排序。 + +我们来通过演示图,更深入的理解一下这个过程。 + +![img](http://dunwu.test.upcdn.net/cs/algorithm/sort/shell-sort.png!zp) + +在上面这幅图中: + +初始时,有一个大小为 10 的无序序列。 + +- 在**第一趟排序中**,我们不妨设 gap1 = N / 2 = 5,即相隔距离为 5 的元素组成一组,可以分为 5 组。 + - 接下来,按照直接插入排序的方法对每个组进行排序。 +- 在** 第二趟排序中**,我们把上次的 gap 缩小一半,即 gap2 = gap1 / 2 = 2 (取整数)。这样每相隔距离为 2 的元素组成一组,可以分为 2 组。 + - 按照直接插入排序的方法对每个组进行排序。 +- 在**第三趟排序中**,再次把 gap 缩小一半,即 gap3 = gap2 / 2 = 1。 这样相隔距离为 1 的元素组成一组,即只有一组。 + - 按照直接插入排序的方法对每个组进行排序。此时,**排序已经结束**。 + +需要注意一下的是,图中有两个相等数值的元素 **5** 和 **5** 。我们可以清楚的看到,在排序过程中,**两个元素位置交换了**。 + +所以,希尔排序是不稳定的算法。 + +**核心代码** + +```java +public void shellSort(int[] list) { + int gap = list.length / 2; + + while (1 <= gap) { + // 把距离为 gap 的元素编为一个组,扫描所有组 + for (int i = gap; i < list.length; i++) { + int j = 0; + int temp = list[i]; + + // 对距离为 gap 的元素组进行排序 + for (j = i - gap; j >= 0 && temp < list[j]; j = j - gap) { + list[j + gap] = list[j]; + } + list[j + gap] = temp; + } + + System.out.format("gap = %d:\t", gap); + printAll(list); + gap = gap / 2; // 减小增量 + } +} +``` + +### 算法分析 + +**希尔排序的算法性能** + +| 参数 | 结果 | +| ------------------ | --------- | +| 排序类别 | 插入排序 | +| 排序方法 | 希尔排序 | +| 时间复杂度平均情况 | O(Nlog2N) | +| 时间复杂度最坏情况 | O(N1.5) | +| 时间复杂度最好情况 | | +| 空间复杂度 | O(1) | +| 稳定性 | 不稳定 | +| 复杂性 | 较复杂 | + +#### 时间复杂度 + +步长的选择是希尔排序的重要部分。只要最终步长为 1 任何步长序列都可以工作。 + +算法最开始以一定的步长进行排序。然后会继续以一定步长进行排序,最终算法以步长为 1 进行排序。当步长为 1 时,算法变为插入排序,这就保证了数据一定会被排序。 + +Donald Shell 最初建议步长选择为 N/2 并且对步长取半直到步长达到 1。虽然这样取可以比 O(N2)类的算法(插入排序)更好,但这样仍然有减少平均时间和最差时间的余地。可能希尔排序最重要的地方在于当用较小步长排序后,以前用的较大步长仍然是有序的。比如,如果一个数列以步长 5 进行了排序然后再以步长 3 进行排序,那么该数列不仅是以步长 3 有序,而且是以步长 5 有序。如果不是这样,那么算法在迭代过程中会打乱以前的顺序,那就不会以如此短的时间完成排序了。 + +已知的最好步长序列是由 Sedgewick 提出的(1, 5, 19, 41, 109,...),该序列的项来自这两个算式。 + +这项研究也表明“比较在希尔排序中是最主要的操作,而不是交换。”用这样步长序列的希尔排序比插入排序和堆排序都要快,甚至在小数组中比快速排序还快,但是在涉及大量数据时希尔排序还是比快速排序慢。 + +#### 算法稳定性 + +由上文的**希尔排序算法演示图**即可知,希尔排序中相等数据可能会交换位置,所以希尔排序是**不稳定**的算法。 + +#### 直接插入排序和希尔排序的比较 + +- 直接插入排序是**稳定的**;而希尔排序是**不稳定**的。 +- 直接插入排序更适合于原始记录基本**有序**的集合。 +- 希尔排序的比较次数和移动次数都要比直接插入排序少,当 N 越大时,效果越明显。 +- 在希尔排序中,增量序列 gap 的取法必须满足:**最后一个步长必须是 1 。 ** +- 直接插入排序也**适用于链式存储结构**;希尔排序**不适用于链式结构**。 + +### 示例代码 + +[我的 Github 测试例](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java) + +样本包含:数组个数为奇数、偶数的情况;元素重复或不重复的情况。且样本均为随机样本,实测有效。 + +## 简单选择排序 + +### 要点 + +> 简单选择排序是一种**选择排序**。 +> +> **选择排序**:每趟从待排序的记录中选出关键字最小的记录,顺序放在已排序的记录序列末尾,直到全部排序结束为止。 + +### 算法思想 + +1. 从待排序序列中,找到关键字最小的元素; +2. 如果最小元素不是待排序序列的第一个元素,将其和第一个元素互换; +3. 从余下的 N - 1 个元素中,找出关键字最小的元素,重复 1、2 步,直到排序结束。 + +如图所示,每趟排序中,将当前**第 i 小的元素放在位置 i **上。 + +**核心代码** + +![img](http://dunwu.test.upcdn.net/cs/algorithm/sort/selection-sort.png!zp) + +### 算法分析 + +**简单选择排序算法的性能** + +| 参数 | 结果 | +| ------------------ | ------------ | +| 排序类别 | 选择排序 | +| 排序方法 | 简单选择排序 | +| 时间复杂度平均情况 | O(N2) | +| 时间复杂度最坏情况 | O(N2) | +| 时间复杂度最好情况 | O(N2) | +| 空间复杂度 | O(1) | +| 稳定性 | 不稳定 | +| 复杂性 | 简单 | + +#### 时间复杂度 + +简单选择排序的比较次数与序列的初始排序无关。 假设待排序的序列有 **N** 个元素,则**比较次数总是 N (N - 1) / 2 **。 + +而移动次数与序列的初始排序有关。当序列正序时,移动次数最少,为 **0**. + +当序列反序时,移动次数最多,为 **3N (N - 1) / 2**。 + +所以,综合以上,简单排序的时间复杂度为 **O(N2)**。 + +#### 空间复杂度 + +简单选择排序需要占用一个临时空间,在交换数值时使用。 + +### 示例代码 + +[我的 Github 测试例](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java) + +样本包含:数组个数为奇数、偶数的情况;元素重复或不重复的情况。且样本均为随机样本,实测有效。 + +## 堆排序 + +### 要点 + +在介绍堆排序之前,首先需要说明一下,堆是个什么玩意儿。 + +**堆**是一棵**顺序存储**的**完全二叉树**。 + +其中每个结点的关键字都**不大于**其孩子结点的关键字,这样的堆称为**小根堆**。 +其中每个结点的关键字都**不小于**其孩子结点的关键字,这样的堆称为**大根堆**。 +举例来说,对于 n 个元素的序列 {R0, R1, ... , Rn} 当且仅当满足下列关系之一时,称之为堆: + +- **Ri <= R2i+1 且 Ri <= R2i+2 (小根堆)** +- **Ri >= R2i+1 且 Ri >= R2i+2 (大根堆)** + +其中 i=1,2,…,n/2 向下取整; + +![img](http://dunwu.test.upcdn.net/cs/algorithm/sort/heap-sort.png!zp) + +如上图所示,序列 R{3, 8,15, 31, 25} 是一个典型的小根堆。 + +堆中有两个父结点,元素 3 和元素 8。 + +元素 3 在数组中以 R[0] 表示,它的左孩子结点是 R[1],右孩子结点是 R[2]。 + +元素 8 在数组中以 R[1] 表示,它的左孩子结点是 R[3],右孩子结点是 R[4],它的父结点是 R[0]。可以看出,它们**满足以下规律**: + +设当前元素在数组中以 **R[i]** 表示,那么, + +- 它的**左孩子结点**是:**R[2\*i+1]**; +- 它的**右孩子结点**是:**R[2\*i+2]**; +- 它的**父结点**是:**R[(i-1)/2]**; +- R[i] <= R[2*i+1] 且 R[i] <= R[2i+2]。 + +### 算法思想 + +- 首先,按堆的定义将数组 R[0..n]调整为堆(这个过程称为创建初始堆),交换 R[0]和 R[n]; +- 然后,将 R[0..n-1]调整为堆,交换 R[0]和 R[n-1]; +- 如此反复,直到交换了 R[0]和 R[1]为止。 + +以上思想可归纳为两个操作: + +1. 根据初始数组去**构造初始堆**(构建一个完全二叉树,保证所有的父结点都比它的孩子结点数值大)。 +2. 每次**交换第一个和最后一个元素,输出最后一个元素**(最大值),然后把剩下元素**重新调整**为大根堆。 + +当输出完最后一个元素后,这个数组已经是按照从小到大的顺序排列了。 + +先通过详细的实例图来看一下,如何构建初始堆。 + +设有一个无序序列 { 1, 3,4, 5, 2, 6, 9, 7, 8, 0 }。 + +![img](http://dunwu.test.upcdn.net/cs/algorithm/sort/heap-sort-02.png!zp) + +构造了初始堆后,我们来看一下完整的堆排序处理: + +还是针对前面提到的无序序列 { 1,3, 4, 5, 2, 6, 9, 7, 8, 0 } 来加以说明。 + +![img](http://dunwu.test.upcdn.net/cs/algorithm/sort/heap-sort-03.png!zp) + +相信,通过以上两幅图,应该能很直观的演示堆排序的操作处理。 + +**核心代码** + +```java +public void HeapAdjust(int[] array2, int parent, int length) { + int temp = array2[parent]; // temp保存当前父节点 + int child = 2 * parent + 1; // 先获得左孩子 + + while (child < length) { + // 如果有右孩子结点,并且右孩子结点的值大于左孩子结点,则选取右孩子结点 + if (child + 1 < length && array2[child] < array2[child + 1]) { + child++; + } + + // 如果父结点的值已经大于孩子结点的值,则直接结束 + if (temp >= array2[child]) + break; + + // 把孩子结点的值赋给父结点 + array2[parent] = array2[child]; + + // 选取孩子结点的左孩子结点,继续向下筛选 + parent = child; + child = 2 * child + 1; + } + + array2[parent] = temp; +} + +public void heapSort(int[] list) { + // 循环建立初始堆 + for (int i = list.length / 2; i >= 0; i--) { + HeapAdjust(list, i, list.length); + } + + // 进行n-1次循环,完成排序 + for (int i = list.length - 1; i > 0; i--) { + // 最后一个元素和第一元素进行交换 + int temp = list[i]; + list[i] = list[0]; + list[0] = temp; + + // 筛选 R[0] 结点,得到i-1个结点的堆 + HeapAdjust(list, 0, i); + System.out.format("第 %d 趟: \t", list.length - i); + printPart(list, 0, list.length - 1); + } +} +``` + +### 算法分析 + +**堆排序算法的总体情况** + +| 参数 | 结果 | +| ------------------ | --------- | +| 排序类别 | 选择排序 | +| 排序方法 | 堆排序 | +| 时间复杂度平均情况 | O(nlog2n) | +| 时间复杂度最坏情况 | O(nlog2n) | +| 时间复杂度最好情况 | O(nlog2n) | +| 空间复杂度 | O(1) | +| 稳定性 | 不稳定 | +| 复杂性 | 较复杂 | + +#### 时间复杂度 + +堆的存储表示是**顺序的**。因为堆所对应的二叉树为完全二叉树,而完全二叉树通常采用顺序存储方式。 + +当想得到一个序列中第 **k** 个最小的元素之前的部分排序序列,最好采用堆排序。 + +因为堆排序的时间复杂度是 **O(n+klog2n)**,若 **k ≤ n/log2n**,则可得到的时间复杂度为 **O(n)**。 + +#### 算法稳定性 + +堆排序是一种**不稳定**的排序方法。 + +因为在堆的调整过程中,关键字进行比较和交换所走的是该结点到叶子结点的一条路径, + +因此对于相同的关键字就可能出现排在后面的关键字被交换到前面来的情况。 + +### 示例代码 + +[我的 Github 测试例](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java) + +样本包含:数组个数为奇数、偶数的情况;元素重复或不重复的情况。且样本均为随机样本,实测有效。 + +## 归并排序 + +### 要点 + +> 归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用**分治法(Divide and Conquer)**的一个非常典型的应用。 +> +> 将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为**二路归并**。 + +### 算法思想 + +将待排序序列 R[0...n-1] 看成是 n 个长度为 1 的有序序列,将相邻的有序表成对归并,得到 n/2 个长度为 2 的有序表;将这些有序序列再次归并,得到 n/4 个长度为 4 的有序序列;如此反复进行下去,最后得到一个长度为 n 的有序序列。 + +综上可知: + +归并排序其实要做两件事: + +- “分解”——将序列每次**折半划分**。 +- “合并”——将划分后的序列段**两两合并后排序**。 + +我们先来考虑第二步,**如何合并**? + +在每次合并过程中,都是对两个有序的序列段进行合并,然后排序。 + +这两个有序序列段分别为 R[low, mid] 和 R[mid+1, high]。 + +先将他们合并到一个局部的**暂存数组**R2 中,带合并完成后再将 R2 复制回 R 中。 + +为了方便描述,我们称 R[low, mid] 第一段,R[mid+1, high] 为第二段。 + +每次从两个段中取出一个记录进行关键字的比较,将较小者放入 R2 中。最后将各段中余下的部分直接复制到 R2 中。 + +经过这样的过程,R2 已经是一个有序的序列,再将其复制回 R 中,一次合并排序就完成了。 + +**核心代码** + +```java +public void Merge(int[] array2, int low, int mid, int high) { + int i = low; // i是第一段序列的下标 + int j = mid + 1; // j是第二段序列的下标 + int k = 0; // k是临时存放合并序列的下标 + int[] array2 = new int[high - low + 1]; // array2是临时合并序列 + + // 扫描第一段和第二段序列,直到有一个扫描结束 + while (i <= mid && j <= high) { + // 判断第一段和第二段取出的数哪个更小,将其存入合并序列,并继续向下扫描 + if (array2[i] <= array2[j]) { + array2[k] = array2[i]; + i++; + k++; + } else { + array2[k] = array2[j]; + j++; + k++; + } + } + + // 若第一段序列还没扫描完,将其全部复制到合并序列 + while (i <= mid) { + array2[k] = array2[i]; + i++; + k++; + } + + // 若第二段序列还没扫描完,将其全部复制到合并序列 + while (j <= high) { + array2[k] = array2[j]; + j++; + k++; + } + + // 将合并序列复制到原始序列中 + for (k = 0, i = low; i <= high; i++, k++) { + array2[i] = array2[k]; + } +} +``` + +掌握了合并的方法,接下来,让我们来了解**如何分解**。 + +![img](http://dunwu.test.upcdn.net/cs/algorithm/sort/merge-sort.png!zp) + +在某趟归并中,设各子表的长度为 **gap**,则归并前 R[0...n-1] 中共有 **n/gap** 个有序的子表:`R[0...gap-1]`, `R[gap...2*gap-1]`, ... , `R[(n/gap)*gap ... n-1]`。 + +调用 Merge **将相邻的子表归并**时,必须对表的特殊情况进行特殊处理。 + +若子表个数为奇数,则最后一个子表无须和其他子表归并(即本趟处理轮空):若子表个数为偶数,则要注意到最后一对子表中后一个子表区间的上限为 n-1。 + +**核心代码** + +```java +public void MergePass(int[] array2, int gap, int length) { + int i = 0; + + // 归并gap长度的两个相邻子表 + for (i = 0; i + 2 * gap - 1 < length; i = i + 2 * gap) { + Merge(array2, i, i + gap - 1, i + 2 * gap - 1); + } + + // 余下两个子表,后者长度小于gap + if (i + gap - 1 < length) { + Merge(array2, i, i + gap - 1, length - 1); + } +} + +public int[] sort(int[] list) { + for (int gap = 1; gap < list.length; gap = 2 * gap) { + MergePass(list, gap, list.length); + System.out.print("gap = " + gap + ":\t"); + this.printAll(list); + } + return list; +} +``` + +### 算法分析 + +**归并排序算法的性能** + +| 参数 | 结果 | +| ------------------ | --------- | +| 排序类别 | 归并排序 | +| 排序方法 | 归并排序 | +| 时间复杂度平均情况 | O(nlog2n) | +| 时间复杂度最坏情况 | O(nlog2n) | +| 时间复杂度最好情况 | O(nlog2n) | +| 空间复杂度 | O(n) | +| 稳定性 | 稳定 | +| 复杂性 | 较复杂 | + +#### 时间复杂度 + +归并排序的形式就是一棵二叉树,它需要遍历的次数就是二叉树的深度,而根据完全二叉树的可以得出它的时间复杂度是 **O(n\*log2n)**。 + +#### 空间复杂度 + +由前面的算法说明可知,算法处理过程中,需要一个大小为 **n** 的临时存储空间用以保存合并序列。 + +#### 算法稳定性 + +在归并排序中,相等的元素的顺序不会改变,所以它是**稳定的**算法。 + +#### 归并排序和堆排序、快速排序的比较 + +若从空间复杂度来考虑:首选堆排序,其次是快速排序,最后是归并排序。 + +若从稳定性来考虑,应选取归并排序,因为堆排序和快速排序都是不稳定的。 + +若从平均情况下的排序速度考虑,应该选择快速排序。 + +### 示例代码 + +[我的 Github 测试例](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java) + +样本包含:数组个数为奇数、偶数的情况;元素重复或不重复的情况。且样本均为随机样本,实测有效。 + +## 基数排序 + +### 要点 + +基数排序与本系列前面讲解的七种排序方法都不同,它**不需要比较关键字的大小**。 + +它是根据关键字中各位的值,通过对排序的 N 个元素进行若干趟“分配”与“收集”来实现排序的。 + +不妨通过一个具体的实例来展示一下,基数排序是如何进行的。 + +设有一个初始序列为: R {50, 123, 543, 187, 49, 30,0, 2, 11, 100}。 + +我们知道,任何一个阿拉伯数,它的各个位数上的基数都是以 0\~9 来表示的。 + +所以我们不妨把 0\~9 视为 10 个桶。 + +我们先根据序列的个位数的数字来进行分类,将其分到指定的桶中。例如:R[0] = 50,个位数上是 0,将这个数存入编号为 0 的桶中。 + +![img](http://dunwu.test.upcdn.net/cs/algorithm/sort/radix-sort.png!zp) + +分类后,我们在从各个桶中,将这些数按照从编号 0 到编号 9 的顺序依次将所有数取出来。 + +这时,得到的序列就是个位数上呈递增趋势的序列。 + +按照个位数排序: {50, 30, 0, 100, 11, 2, 123,543, 187, 49}。 + +接下来,可以对十位数、百位数也按照这种方法进行排序,最后就能得到排序完成的序列。 + +### 算法分析 + +**基数排序的性能** + +| 参数 | 结果 | +| ------------------ | --------- | +| 排序类别 | 基数排序 | +| 排序方法 | 基数排序 | +| 时间复杂度平均情况 | O(d(n+r)) | +| 时间复杂度最坏情况 | O(d(n+r)) | +| 时间复杂度最好情况 | O(d(n+r)) | +| 空间复杂度 | O(n+r) | +| 稳定性 | 稳定 | +| 复杂性 | 较复杂 | + +#### 时间复杂度 + +通过上文可知,假设在基数排序中,r 为基数,d 为位数。则基数排序的时间复杂度为 **O(d(n+r))**。 + +我们可以看出,基数排序的效率和初始序列是否有序没有关联。 + +#### 空间复杂度 + +在基数排序过程中,对于任何位数上的基数进行“装桶”操作时,都需要 **n+r** 个临时空间。 + +#### 算法稳定性 + +在基数排序过程中,每次都是将当前位数上相同数值的元素统一“装桶”,并不需要交换位置。所以基数排序是**稳定**的算法。 + +### 示例代码 + +[我的 Github 测试例](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java) + +样本包含:数组个数为奇数、偶数的情况;元素重复或不重复的情况。且样本均为随机样本,实测有效。 diff --git a/数据结构/trie.md b/数据结构/trie.md new file mode 100644 index 00000000..ba089b05 --- /dev/null +++ b/数据结构/trie.md @@ -0,0 +1,201 @@ +# 字典树 + +Trie,又称前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。 + +字典树设计的核心思想是空间换时间,所以数据结构本身比较消耗空间。但它利用了字符串的**共同前缀(Common Prefix)**作为存储依据,以此来节省存储空间,并加速搜索时间。Trie 的字符串搜索时间复杂度为 **O(m)**,m 为最长的字符串的长度,其查询性能与集合中的字符串的数量无关。其在搜索字符串时表现出的高效,使得特别适用于构建文本搜索和词频统计等应用。 + +## Trie 的性质 + +- 根节点(Root)不包含字符,除根节点外的每一个节点都仅包含一个字符; +- 从根节点到某一节点路径上所经过的字符连接起来,即为该节点对应的字符串; +- 任意节点的所有子节点所包含的字符都不相同; + +## Trie 的查找过程 + +1. 每次从根结点开始搜索; +2. 获取关键词的第一个字符,根据该字符选择对应的子节点,转到该子节点继续检索; +3. 在相应的子节点上,获取关键词的第二个字符,进一步选择对应的子节点进行检索; +4. 以此类推,进行迭代过程; +5. 在某个节点处,关键词的所有字母已被取出,则读取附在该节点上的信息,查找完成。 + +## Trie 的应用 + +(1)自动补全 + +![img](http://dunwu.test.upcdn.net/snap/20200305095300.png) + +(2)拼写检查 + +![img](http://dunwu.test.upcdn.net/snap/20200305101637.png) + +(3)IP 路由 (最长前缀匹配) + +![img](http://dunwu.test.upcdn.net/snap/20200305102959.gif) + +图 3. 使用 Trie 树的最长前缀匹配算法,Internet 协议(IP)路由中利用转发表选择路径。 + +(4)T9 (九宫格) 打字预测 + +![img](http://dunwu.test.upcdn.net/snap/20200305103047.jpg) + +(5)单词游戏 + +![img](http://dunwu.test.upcdn.net/snap/20200305103052.png) + +图 5. Trie 树可通过剪枝搜索空间来高效解决 Boggle 单词游戏 + +还有其他的数据结构,如平衡树和哈希表,使我们能够在字符串数据集中搜索单词。为什么我们还需要 Trie 树呢?尽管哈希表可以在 O(1)O(1) 时间内寻找键值,却无法高效的完成以下操作: + +- 找到具有同一前缀的全部键值。 +- 按词典序枚举字符串的数据集。 + +Trie 树优于哈希表的另一个理由是,随着哈希表大小增加,会出现大量的冲突,时间复杂度可能增加到 $$O(n)$$,其中 n 是插入的键的数量。与哈希表相比,Trie 树在存储多个具有相同前缀的键时可以使用较少的空间。此时 Trie 树只需要 $$O(m)$$ 的时间复杂度,其中 m 为键长。而在平衡树中查找键值需要 $$O(mlogn)$$ 时间复杂度。 + +## Trie 树的结点结构 + +Trie 树是一个有根的树,其结点具有以下字段:。 + +最多 R 个指向子结点的链接,其中每个链接对应字母表数据集中的一个字母。 + +- 本文中假定 R 为 26,小写拉丁字母的数量。 +- 布尔字段,以指定节点是对应键的结尾还是只是键前缀。 + +![3463d9e7cb323911aa67cbd94910a34d88c9402a1ab41bbea10852cd0a74f2af-file_1562596867185](http://dunwu.test.upcdn.net/snap/20200305103530.png) + +```java +class TrieNode { + + // R links to node children + private TrieNode[] links; + + private final int R = 26; + + private boolean isEnd; + + public TrieNode() { + links = new TrieNode[R]; + } + + public boolean containsKey(char ch) { + return links[ch -'a'] != null; + } + public TrieNode get(char ch) { + return links[ch -'a']; + } + public void put(char ch, TrieNode node) { + links[ch -'a'] = node; + } + public void setEnd() { + isEnd = true; + } + public boolean isEnd() { + return isEnd; + } +} +``` +向 Trie 树中插入键 + +我们通过搜索 Trie 树来插入一个键。我们从根开始搜索它对应于第一个键字符的链接。有两种情况: + +- 链接存在。沿着链接移动到树的下一个子层。算法继续搜索下一个键字符。 +- 链接不存在。创建一个新的节点,并将它与父节点的链接相连,该链接与当前的键字符相匹配。 + +重复以上步骤,直到到达键的最后一个字符,然后将当前节点标记为结束节点,算法完成。 + +图 7. 向 Trie 树中插入键 + +Java +class Trie { + private TrieNode root; + + public Trie() { + root = new TrieNode(); + } + + // Inserts a word into the trie. + public void insert(String word) { + TrieNode node = root; + for (int i = 0; i < word.length(); i++) { + char currentChar = word.charAt(i); + if (!node.containsKey(currentChar)) { + node.put(currentChar, new TrieNode()); + } + node = node.get(currentChar); + } + node.setEnd(); + } +} +复杂度分析 + +时间复杂度:O(m)O(m),其中 mm 为键长。在算法的每次迭代中,我们要么检查要么创建一个节点,直到到达键尾。只需要 mm 次操作。 + +空间复杂度:O(m)O(m)。最坏的情况下,新插入的键和 Trie 树中已有的键没有公共前缀。此时需要添加 mm 个结点,使用 O(m)O(m) 空间。 + +在 Trie 树中查找键 +每个键在 trie 中表示为从根到内部节点或叶的路径。我们用第一个键字符从根开始,。检查当前节点中与键字符对应的链接。有两种情况: + +存在链接。我们移动到该链接后面路径中的下一个节点,并继续搜索下一个键字符。 +不存在链接。若已无键字符,且当前结点标记为 isEnd,则返回 true。否则有两种可能,均返回 false : +还有键字符剩余,但无法跟随 Trie 树的键路径,找不到键。 +没有键字符剩余,但当前结点没有标记为 isEnd。也就是说,待查找键只是Trie树中另一个键的前缀。 + + +图 8. 在 Trie 树中查找键 + +Java +class Trie { + ... + + // search a prefix or whole key in trie and + // returns the node where search ends + private TrieNode searchPrefix(String word) { + TrieNode node = root; + for (int i = 0; i < word.length(); i++) { + char curLetter = word.charAt(i); + if (node.containsKey(curLetter)) { + node = node.get(curLetter); + } else { + return null; + } + } + return node; + } + + // Returns if the word is in the trie. + public boolean search(String word) { + TrieNode node = searchPrefix(word); + return node != null && node.isEnd(); + } +} +复杂度分析 + +时间复杂度 : O(m)O(m)。算法的每一步均搜索下一个键字符。最坏的情况下需要 mm 次操作。 +空间复杂度 : O(1)O(1)。 +查找 Trie 树中的键前缀 +该方法与在 Trie 树中搜索键时使用的方法非常相似。我们从根遍历 Trie 树,直到键前缀中没有字符,或者无法用当前的键字符继续 Trie 中的路径。与上面提到的“搜索键”算法唯一的区别是,到达键前缀的末尾时,总是返回 true。我们不需要考虑当前 Trie 节点是否用 “isend” 标记,因为我们搜索的是键的前缀,而不是整个键。 + + + +图 9. 查找 Trie 树中的键前缀 + +Java +class Trie { + ... + + // Returns if there is any word in the trie + // that starts with the given prefix. + public boolean startsWith(String prefix) { + TrieNode node = searchPrefix(prefix); + return node != null; + } +} +复杂度分析 + +时间复杂度 : O(m)O(m)。 +空间复杂度 : O(1)O(1)。 + +## 实战 + +## 参考资料 + +- https://leetcode-cn.com/problems/implement-trie-prefix-tree/solution/shi-xian-trie-qian-zhui-shu-by-leetcode/D:\Codes\ZPTutorial\ZPSpring\spring-boot-tutorial\codes\spring-boot-dubbo\README.md diff --git a/数据结构/双链表的数据存储.png b/数据结构/双链表的数据存储.png new file mode 100644 index 00000000..4fdb70bf Binary files /dev/null and b/数据结构/双链表的数据存储.png differ diff --git a/数据结构/堆栈.png b/数据结构/堆栈.png new file mode 100644 index 00000000..bb67d4be Binary files /dev/null and b/数据结构/堆栈.png differ diff --git a/数据结构/循环单向链表.png b/数据结构/循环单向链表.png new file mode 100644 index 00000000..8f39fe69 Binary files /dev/null and b/数据结构/循环单向链表.png differ diff --git a/数据结构/循环双向链表内存.png b/数据结构/循环双向链表内存.png new file mode 100644 index 00000000..66d8202c Binary files /dev/null and b/数据结构/循环双向链表内存.png differ diff --git a/数据结构/循环队列.png b/数据结构/循环队列.png new file mode 100644 index 00000000..bcce939a Binary files /dev/null and b/数据结构/循环队列.png differ diff --git a/数据结构/数据结构与算法.jpeg b/数据结构/数据结构与算法.jpeg new file mode 100644 index 00000000..c6b7d82d Binary files /dev/null and b/数据结构/数据结构与算法.jpeg differ diff --git a/数据结构/树.png b/数据结构/树.png new file mode 100644 index 00000000..46b39ce9 Binary files /dev/null and b/数据结构/树.png differ diff --git a/算法/A类:基本算法/1 算法概述.md b/算法/A类:基本算法/1 算法概述.md index 332db835..6e3e6b13 100644 --- a/算法/A类:基本算法/1 算法概述.md +++ b/算法/A类:基本算法/1 算法概述.md @@ -6,11 +6,11 @@ 算法是一系列解决问题的清晰指令,对于符合一定规范的输入,算法能够在有限时间内获得所要求的输出。算法是解决问题的一种方法或过程,它是由若干条指令组成的有穷序列。 ### 特征 -输入:有零或多个外部量作为算法的输入。 -输出:算法产生至少一个量作为输出。 -确定性:组成算法的每条指令清晰、无歧义。 -有效性:算法中执行的任何计算步骤都是可以被分解为基本的可执行的操作步。 -有限性:算法中每条指令的执行次数有限,执行每条指令的时间也有限。 +* 输入:有零或多个外部量作为算法的输入。 +* 输出:算法产生至少一个量作为输出。 +* 确定性:组成算法的每条指令清晰、无歧义。 +* 有效性:算法中执行的任何计算步骤都是可以被分解为基本的可执行的操作步。 +* 有限性:算法中每条指令的执行次数有限,执行每条指令的时间也有限。 ### 算法的描述 * 算法的描述方式:自然语言、流程图、伪代码 @@ -20,19 +20,31 @@ ## 2 求解流程 ### 求解步骤 +> 在没有熟练之前,应该前置按照这样的算法解决流程,解题。 ![](image/算法流程.png) -1. 理解问题 -2. 选择策略 -3. 算法设计 -4. 正确性证明 -5. 算法分析 -6. 程序设计 +1. **理解问题**(确定**问题抽象**(将应用问题抽象为数学问题)、**问题分类**(搜索、排序。)) +2. **选择策略**(选择合适的**数据结构**、选择合适的**算法思想**) +3. **算法设计**(设计**算法流程**,伪代码) +4. **正确性证明**(查看伪代码的流程的正确性,**算法特例**) +5. **算法分析**(分析算法**执行效率**) +6. **程序设计**(编程) + +### 更详细的说明 + +> 自己在处理一个题之前到底应该做哪些事情,或者说,按照怎样的流程?是否应该把这些流程写下来?或者按照某种套路来,将会事半功倍。 + +* 理解问题、问题分析,问题抽象和问题分类。应该将一个问题,归为某一个类别。问题类别,应该属于某个算法思想下的。也就是说,一个问题类别,应该用某种算法思想来解决。但是两者并不是完全重合。 +* 选择策略,主要是选择使用哪种算法思想。当确定了问题的类别之后。就可以确定其算法思想了。或者说,算法四线和问题类别应该是同时确定的。接下来需要做的是,设计一些静态的东西。例如数据结构等。 +* 算法设计,在静态结构上添加流程。 +* 正确性证明,主要分析特例和为考虑到的特殊情况。尽可能举反例,同时完善设计好的算法。 +* 算法分析,主要分析算法的执行效率上的可行性。 +* 程序设计 ## 3 算法分类 -### 面向算法范式 +### 面向算法思想 > 这一个主要是我们算法课程中学习到的算法。用来解决基本具体的问题。 * 蛮力法(暴力破解、枚举、穷举) * 递归与分治法 @@ -58,7 +70,7 @@ * 冒泡排序 * 快速排序 * 桶排序 -* 查找 +* 查找(匹配) * 顺序查找 * 二分查找 * 插值查找 diff --git a/算法/A类:基本算法/3.9 位运算算法.md b/算法/A类:基本算法/3.9 位运算算法.md new file mode 100644 index 00000000..bf3a783c --- /dev/null +++ b/算法/A类:基本算法/3.9 位运算算法.md @@ -0,0 +1,45 @@ +> 主要是利用位运算解决一些巧妙的问题。 +& ^ ~ | + +* n & (n - 1) 会把n中的最后一个1变成0 +* 相同的数 ^抑或运算等于零。不同的数^抑或运算等于1 + +向下整除 n // 2n//2 等价于 右移一位 n >> 1n>>1 ; +取余数 n \% 2n%2 等价于 判断二进制最右一位值 n \& 1n&1 + + +### 屏蔽计算,作为递归的终止条件 + +### 快速幂 + +``` +double myPow(double x, int n) { + //使用二进制方法,将幂转换成二进制。二进制每个位的权重就是可以递推计算,与二分法效果相同。 + long long N=n; + if(N<0){ + x=1/x; + N=-N; + } + double result=1; + double temp=x; + while(N!=0){ + if( N & 1)result=result*temp; + temp *=temp; + N= N>>1; + } + return result; +``` + +### 快速乘法 + +``` +int quickMulti(int A, int B) { + int ans = 0; + while(B) { + if (B & 1) ans += A; + A <<= 1; + B >>= 1 + } + return ans; +} +``` \ No newline at end of file diff --git a/算法/刷题笔记/算法模板.md b/算法/刷题笔记/算法模板.md new file mode 100644 index 00000000..97f82b6b --- /dev/null +++ b/算法/刷题笔记/算法模板.md @@ -0,0 +1,123 @@ +# 算法代码模板 + +> 算法代码模板即算法的常见套路。熟练记忆,活学活用。 + +## 递归 + +```java +public void recursion(int level, int param1, int param2, ...) { + // 递归终止条件 + if (level > MAX_LEVEL) { + // print + return; + } + + // 当前处理逻辑 + processData(level, param1, param2, ...); + + // 递归 + recursion(level + 1, param1, param2, ...); + + // 如有必要,还原状态 + reverseState(level, data); +} +``` + +## DFS + +```java +Set visited = new HashSet<>(); + +public void dfs(Node node, Set visited) { + visited.add(node); + for (Node n : node.children) { + if (!visited.contains(n)) { + dfs(n, visited); + } + } +} +``` + +## BFS + +```java +public List> bfs(Node root) { + List> list = new ArrayList<>(); + Queue queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + List levelList = new ArrayList<>(); + + int size = queue.size(); + // 遍历当前层级所有节点 + for (int i = 0; i < size; i++) { + Node n = queue.poll(); + + // 对节点 n 做逻辑处理 + levelList.add(n.val); + + // 将 n 的所有节点加入队列 + for (Node c : n.children) { + queue.offer(c); + } + } + + list.add(levelList); + } + + return list; +} +``` + +## 二分查找 + +数组的二分查找: + +```java +int left = 0, right = nums.length - 1; +while (left <= right) { + int mid = left + (right - left) / 2; // 防止数据类型溢出 + if (nums[mid] == target) { + break or return result; + } else if (nums[mid] < target) { + left = mid + 1; + } else { + right = mid - 1; + } +} +``` + +## 动态规划 + +```java +// DP 状态定义 +int[][] dp = new int[m + 1][n + 1]; + +// 初始状态 +dp[0][0] = x; +dp[0][1] = y; + +// DP 状态推导 +for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + // 方程根据实际场景推导 + dp[i][j] = max or min { dp[i - 1][j], dp[i][j - 1], ... } + } +} + +// 返回最优解 +return dp[m][n]; +``` + +## 位运算 + +记忆常用位运算公式(无他,背就完事了) + +| | 二进制表达式 | 等价表达式 | +| -------------- | ----------------------------- | ----------------------------- | +| 判断奇偶 | `x & 1 == 1`
`x & 1 == 0` | `x % 2 == 1`
`x % 2 == 0` | +| 清零最低位的 1 | `x = x & (x - 1)` | | +| 得到最低位的 1 | `x & -x` | | + + + diff --git a/算法/考前复习知识点.md b/算法/考前复习知识点.md index bbad3f7e..bb3da0bb 100644 --- a/算法/考前复习知识点.md +++ b/算法/考前复习知识点.md @@ -1,32 +1,34 @@ -PPT1 -目录 -算法效率 -分类搜索匹配 -分治 -动态规划 -贪心算法 -回溯法 -分支限界 -NP问题 -自适应算法 -随机化算法 -启发式算法 +### PPT1 +> 目录 +> * 算法效率 +> * 分类搜索匹配 +> * 分治 +> * 动态规划 +> * 贪心算法 +> * 回溯法 +> * 分支限界 +> * NP问题 +> * 自适应算法 +> * 随机化算法 +> * 启发式算法 算法定义:对于每一个合法输入,算法都会在有限的时间内输出一个满足要求的结果 -O(1) constant -O(log n) logarithmic -O(n) linear -O(n log n) n log n -O(n2) quadratic -O(n3) cubic -O(2n) exponential -O(n!) factorial +|复杂度 |说明| +|----|----| +O(1) | constant +O(log n) | logarithmic +O(n) | linear +O(n log n) | n log n +O(n2) | quadratic +O(n3) | cubic +O(2n) | exponential +O(n!) | factorial -PPT2 +### PPT2 PRIM 算法。在网络中寻找最短通信网络。即最小生成树。 -PPT3 +### PPT3 KRUSKAL算法。在网络上中寻找最短通信网络。即最小生成树。 -PPT4 +### PPT4 分治法。 \ No newline at end of file diff --git a/算法/单源最短路问题.md b/算法/补充/单源最短路问题.md similarity index 100% rename from 算法/单源最短路问题.md rename to 算法/补充/单源最短路问题.md