diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..cfe0b71f --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: https://raw.githubusercontent.com/frank-lam/fullstack-tutorial/master/assets/wechat/wx-green.png diff --git a/README.md b/README.md index 50648411..0a492743 100644 --- a/README.md +++ b/README.md @@ -16,19 +16,15 @@ 『技术博客』:www.frankfeekr.cn | 『开源贡献』:⊱ 英雄招募令 | 『微信订阅号』:全栈开发社区

- - -
- -
+

-## 技术学习清单 +🔥🔥🔥 -在这里,为你推荐了技术学习路上优质的学习书籍、学习课程。在推荐清单中将附带官网链接,图书则附京东和豆瓣链接。如果你有更好的学习资料,可以在下方留言,采纳后我会更新在文章中。 +欢迎光临 LinTools 开发者的在线导航: https://tools.frankfeekr.cn -**传送门**:https://mp.weixin.qq.com/s/gezKXKY4ezU0ZbvPybbbxQ +如果你有更好的在线工具,[请点击留言](https://github.com/frank-lam/fullstack-tutorial/issues/65),持续更新! @@ -107,21 +103,16 @@ ## 三、Python - [Python 语言基础](notes/Python/Python简介及基础语法.md) -- Scrapy 爬虫框架 -- Flask -- Django ## 四、前端 - [前端知识体系](notes/Frontend/前端知识体系.md) -- Vue - - 第一个 Vue 应用程序 - - Vue-cli 脚手架 - - vue-router 路由 - - vuex 状态管理 -- Webpack +- [Angular 基础知识](notes/Frontend/Angular.md) +- [ES6+ 语法全解析](https://notes.frankfeekr.cn/docs/frontend/es6/%E9%A1%B9%E7%9B%AE%E5%87%86%E5%A4%87/%E5%89%8D%E8%A8%80) + +
@@ -184,8 +175,6 @@ - - ## 五、数据库 - [MySQL](notes/MySQL.md) @@ -204,9 +193,9 @@ 一个开源的关系数据库,是从伯克利写的 POSTGRES 软件包发展而来的 -- MongoDB +- [InfluxDB](https://www.frankfeekr.cn/2019/07/24/influxdb-tutorial-start/) - 基于分布式文件存储的数据库 + 玩转时序数据库 @@ -236,7 +225,6 @@ web前后端漏洞分析与防御,XSS 攻击、CSRF 攻击、DDoS 攻击、SQL 注入 -- Socket 网络编程 @@ -259,11 +247,6 @@ 深入浅出 Kafka,将用最极简的语言带你走进 Kafka 的消息中间件世界 -- [玩转时序数据库 InfluxDB(一)初体验](https://www.frankfeekr.cn/2019/07/24/influxdb-tutorial-start/) - - - - 【说明】**分布式专题** 笔者也在学习中,这里列举了一些技能列表,笔者将局部更新。敬请期待 @@ -434,7 +417,6 @@ Copyright (c) 2021-present, Frank Lam -

在颠覆世界的同时,也要好好关照自己。 @@ -447,5 +429,5 @@ Copyright (c) 2021-present, Frank Lam from zero to hero.

-
+
diff --git a/assets/wx_group_qrcode.png b/assets/wx_group_qrcode.png index 51030fcc..44479f4d 100644 Binary files a/assets/wx_group_qrcode.png and b/assets/wx_group_qrcode.png differ diff --git a/assets/zhishixingqiu.JPG b/assets/zhishixingqiu.JPG new file mode 100644 index 00000000..afdc791a Binary files /dev/null and b/assets/zhishixingqiu.JPG differ diff --git a/notes/Frontend/Angular.md b/notes/Frontend/Angular.md new file mode 100644 index 00000000..24315da9 --- /dev/null +++ b/notes/Frontend/Angular.md @@ -0,0 +1,868 @@ +# Angular + +## 1. 框架背景 + +Angular 是一个由 Google维护的开源JavaScript框架,用于在HTML和JavaScript中构建Web应用程序,是三大框架之首。 + +不管是1还是2,Angular最显著的特征就是其**整合性**。涵盖了M、V、C/VM等各个层面,不需要组合、评估其它技术就能完成大部分前端开发任务。这样可以有效降低决策成本,提高决策速度,对需要快速起步的团队是非常有帮助的。 + +由于它是从一个用来做原型的框架演化而来的,加上诞生时间很早(2009年,作为对比,jQuery诞生于2006年),当时生态不完善,连模块化机制都得自己实现。 + +但Angular 2就不同了,发布于2016年9月份,它是基于ES6来开发的,它的起点更高,整合了现代前端的各种先进理念,在框架、文档、工具等各个层面提供了全方位的支持 + +在Angular 中最具特色的就是**依赖注入**系统了,它把后端开发中用来解决复杂问题、实现高弹性设计的技术引入了前端 + + + +## 2. Angular CLI + +### 2.1 安装 + +​ Angular CLI用于简单,快速构建Angular2项目,只要掌握几行命令就能扣减前端架构。依赖于NodeJs和npm。 + +```jsx +//安装脚手架 +npm install -g angular-cli +//创建项目 +ng new project_name(项目名称) +//启动项目 +cd project_name +ng serve --open +``` + + + +### 2.2 主要特性 + +1. Angular CLI 可以快速搭建框架,创建module,service,class,directive等; +2. 具有webpack的功能,代码分割,按需加载; +3. 代码打包压缩; +4. 模块测试; +5. 热部署,有改动立即重新编译,不用刷新浏览器;而且速度很快 +6. 有开发环境,测试环境,生产环境的配置,不用自己操心; +7. sass,less的预编译Angular CLI都会自动识别后缀来编译; +8. typescript的配置,Angular CLI在创建应用时都可以自己配置; +9. 在创建好的工程也可以做一些个性化的配置,webpack的具体配置还不支持,未来可能会增加; +10. Angular CLI创建的工程结构是最佳实践,生产可用; + + + +### 2.3 创建module,component,service,class + + + +​ + +## 3. 架构 + +### 3.1 模块 + +​ 模块组件的特征在于可以用于执行单个任务的代码块。 您可以从代码(类)中导出值。 Angular应用程序被称为模块,并使用许多模块构建您的应用程序。 Angular 的基本构建块是可以从模块导出的**组件**类。 + +```js +export class AppComponent { + title = '朝夕教育'; +} + +``` + +### 3.2 组件 + +组件是拥有模板的控制器类,主要处理页面上的应用程序和逻辑的视图。 组件可以拥有独立的样式。 +注册组件,使用 *@Component* 注释,可以将应用程序拆分为更小的部分。 + +#### 3.2.1 创建组件 + +使用ng命令`ng generate component `创建的组件会自动生成在`app.module`中的引用,推荐使用ng命令生成组件 + +```jsx +//快速创建 +ng g c xxx +``` + +```jsx +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'] +}) +``` + +@Component最常用的几个选项: + +**selector**:这个 CSS 选择器用于在模板中标记出该指令,并触发该指令的实例化。 + +**template**:组件的内联模板 + +**templateUrl**:组件模板文件的 URL + +**styleUrls**:组件样式文件 + +**styles**:组件内联样式 + + + +#### 3.2.2 组件生命周期 + +​ Angular 会按以下顺序执行钩子方法。你可以用它来执行以下类型的操作。 + +| 钩子方法 | 用途 | 时机 | +| :------------------------ | :----------------------------------------------------------- | :----------------------------------------------------------- | +| `ngOnChanges()` | 当 Angular 设置或重新设置数据绑定的输入属性时响应。 该方法接受当前和上一属性值的 `SimpleChanges` 对象注意,这发生的非常频繁,所以你在这里执行的任何操作都会显著影响性能。 | 在 `ngOnInit()` 之前以及所绑定的一个或多个输入属性的值发生变化时都会调用。注意,如果你的组件没有输入,或者你使用它时没有提供任何输入,那么框架就不会调用 `ngOnChanges()`。 | +| `ngOnInit()` | 在 Angular 第一次显示数据绑定和设置指令/组件的输入属性之后,初始化指令/组件。 | 在第一轮 `ngOnChanges()` 完成之后调用,只调用**一次**。 | +| `ngDoCheck()` | 检测,并在发生 Angular 无法或不愿意自己检测的变化时作出反应。 | 紧跟在每次执行变更检测时的 `ngOnChanges()` 和 首次执行变更检测时的 `ngOnInit()` 后调用。 | +| `ngAfterContentInit()` | 当 Angular 把外部内容投影进组件视图或指令所在的视图之后调用。 | 第一次 `ngDoCheck()` 之后调用,只调用一次。 | +| `ngAfterContentChecked()` | 每当 Angular 检查完被投影到组件或指令中的内容之后调用 | `ngAfterContentInit()` 和每次 `ngDoCheck()` 之后调用 | +| `ngAfterViewInit()` | 当 Angular 初始化完组件视图及其子视图或包含该指令的视图之后调用 | 第一次 `ngAfterContentChecked()` 之后调用,只调用一次。 | +| `ngAfterViewChecked()` | 每当 Angular 做完组件视图和子视图或包含该指令的视图的变更检测之后调用。 | `ngAfterViewInit()` 和每次 `ngAfterContentChecked()` 之后调用。 | +| `ngOnDestroy()` | 每当 Angular 每次销毁指令/组件之前调用并清扫。 在这儿反订阅可观察对象和分离事件处理器,以防内存泄漏 | 在 Angular 销毁指令或组件之前立即调用。 | + +#### 3.2.3 组件交互 + +1. ​ **@Input** + + 父组件通过`@Input`给子组件绑定属性设置输入类数据 + + ```jsx + //父组件 + + + //子组件 + import { Component, Input } from '@angular/core'; + @Input() + name!: string; + ngOnInit(): void { + console.log(this.name) + } + ``` + +2. ​ **@Output** + + ​ 父组件给子组件传递一个事件,子组件通过`@Output`弹射触发事件 + + ```jsx + //父组件 + + + list:number[] = [1,2,3,4] + addListFun(num:number){ + this.list.push(num) + } + + //子组件 + import { Component, Output,EventEmitter } from '@angular/core'; + @Output() addList = new EventEmitter() + pushList(v:string){ + console.log(this.inpValue) + this.addList.emit(v) + } + ``` + +3. ​ **@ViewChild()** + + 通过`ViewChild`获取子组件实例,获取子组件数据 + + ```jsx + + + + + @ViewChild('myChild') child: any; + constructor() { } + + ngOnInit(): void { + } + getView(e:any){ + console.log(this.child) + this.child.setInpValue('我是一段数据') + } + ``` + + + +### 3.3 模板 + +在 Angular 中,**模板**就是一块 HTML。在模板中,你可以通过一种特殊语法来使用 Angular 的许多功能 + +#### 3.3.1 插值语法 + +​ 所谓 "插值" 是指将表达式嵌入到标记文本中。 默认情况下,插值会用双花括号 `{{` 和 `}}` 作为分隔符 + +```js +

hello {{ name }}

+``` + +​ 花括号之间的文本通常是组件属性的名字。Angular 会把这个名字替换为响应组件属性的字符串值 + +​ 括号间的素材是一个**模板表达式**我们可以在`{{}}`内编写js运算 + +```jsx +

hello {{ 1+1 }}

+``` + +#### 3.3.2 属性绑定 + +1. **Attribute绑定** + + ```jsx +

hello {{ 1+1 }}

+ ``` + +2. **类绑定** + + ```jsx + //单一类绑定 +

hello {{ 1+1 }}

+ + //多重类绑定 +

hello {{ 1+1 }}

+

hello {{ 1+1 }}

+

hello {{ 1+1 }}

+ + //ngClass + export class AppComponent { + isActive = true; + } + +

hello {{ 1+1 }}

+ + ``` + +3. **样式绑定** + + ```jsx + //单一样式绑定 +

hello {{ 1+1 }}

+ + //带单位的单一样式绑定 +

hello {{ 1+1 }}

+ + //多重样式绑定 +

hello {{ 1+1 }}

+

hello {{ 1+1 }}

+ + //ngStyle + export class AppComponent { + isMax = false; + } +

hello {{ 1+1 }}

+

hello {{ 1+1 }}

+ ``` + +#### 3.3.3 条件判断 + +​ ***ngIf**是直接影响元素是否被渲染,而非控制元素的显示和隐藏 + +```jsx +export class AppComponent { + isMax = false; + isMin = false; +} +
Max title
+
Min title
+ +//解析完 + +
Max title
+
+ + + isMax为true + + + isMax为false + +``` + +#### 3.3.4 循环语句 + +​ 解析器会把 `let color`、`let i` 和 `let odd` 翻译成命名变量 `color`、`i` 和 `odd` + +微语法解析器接收of,会将它的首字母大写(Of),然后加上属性的指令名(ngFor)前缀,它最终生成的名字是 ngFor 的输入属性(colors) + +`NgFor` 指令在列表上循环,每个循环中都会设置和重置它自己的上下文对象上的属性。 这些属性包括 `index` 和 `odd` 以及一个特殊的属性名 `$implicit`(隐式变量) + +Angular 将 let-color 设置为此上下文中 $implicit 属性的值, 它是由 NgFor 用当前迭代中的 colors 初始化的 + +```jsx +export class AppComponent { + colors:Array = [ 'red', 'blue', 'yellow', 'green' ]; +} + +
+ {{odd}} + {{i}} + {{color}} +
+ +//解析完 + +
{{odd}} {{i}} {{color}}
+
+``` + +```jsx +export class AppComponent { + status = 1; +} + +
    +
  • 已支付
  • +
  • 订单已经确认
  • +
  • 已发货
  • +
  • 无效
  • +
+``` + +#### 3.3.5 事件绑定 + +​ Angular 的事件绑定语法由等号左侧括号内的目标事件名和右侧引号内的模板语句组成。目标事件名是 `click` ,模板语句是 `onSave()` + +事件对象通过`$event`传递 + +```jsx +export class AppComponent { + onSave(){ + console.log('点击了按钮') + } +} + + +``` + +#### 3.3.6 双向绑定 + +双向绑定是应用中的组件共享数据的一种方式。使用双向绑定绑定来侦听事件并在父组件和子组件之间同步更新值 + +ngModel指令**只对表单元素有效**,所以在使用之前需要导入`FormsModule`板块 + +```jsx +import { FormsModule } from '@angular/forms'; + +@NgModule({ + // 申明组件内用到的视图 + declarations: [ + AppComponent, + HelloComponent, + ], + //引入模块需要的类 + imports: [ + BrowserModule, + AppRoutingModule, + FormsModule + ], + //全局服务 + providers: [], + //根组件 + bootstrap: [AppComponent] +}) +``` + +```jsx +export class AppComponent { + userName=''; +} +
+ 输入: +

你输入了: {{userName}}

+
+``` + +#### 3.3.7 模板引用变量 + +模板变量可以帮助你在模板的另一部分使用这个部分的数据。使用模板变量,你可以执行某些任务,比如响应用户输入或微调应用的表单 + +在模板中,要使用井号 `#` 来声明一个模板变量。下列模板变量 `#userName` 语法在 `` 元素上声明了一个名为 `userName` 的变量 + +```html + +``` + +可以在组件模板中的任何地方引用某个模板变量 + +```jsx + + + +export class AppComponent { + callUserName(v){ + console.log(v) + } +} +``` + +Angular 根据你所声明的变量的位置给模板变量赋值: + +- 如果在组件上声明变量,该变量就会引用该组件实例。 +- 如果在标准的 HTML 标记上声明变量,该变量就会引用该元素。 +- 如果你在 `` 元素上声明变量,该变量就会引用一个 `TemplateRef` 实例来代表此模板。 + +#### 3.3.8 表单控件 + +使用表单控件有三个步骤。 + +1. 在你的应用中注册响应式表单模块。该模块声明了一些你要用在响应式表单中的指令。 +2. 生成一个新的 `FormControl` 实例,并把它保存在组件中。 +3. 在模板中注册这个 `FormControl`。 + +**注册响应式表单模块** + +要使用响应式表单控件,就要从 `@angular/forms` 包中导入 `ReactiveFormsModule`,并把它添加到你的 NgModule 的 `imports` 数组中。 + +```js +import { ReactiveFormsModule } from '@angular/forms'; + +@NgModule({ + imports: [ + // other imports ... + ReactiveFormsModule + ], +}) +export class AppModule { } +``` + +要注册一个表单控件,就要导入 `FormControl` 类并创建一个 `FormControl` 的新实例,将其保存为类的属性。 + +```js +import { Component } from '@angular/core'; +import { FormControl } from '@angular/forms'; + +@Component({ + selector: 'app-name-editor', + templateUrl: './name-editor.component.html', + styleUrls: ['./name-editor.component.css'] +}) +export class NameEditorComponent { + name = new FormControl(''); +} + +//使用这种模板绑定语法,把该表单控件注册给了模板中名为 name 的输入元素。这样,表单控件和 DOM 元素就可以互相通讯了:视图会反映模型的变化,模型也会反映视图中的变化 + + +

+ Value: {{ name.value }} +

+``` + +修改name值可以通过`FormControl` 提供的 `setValue()` 方法 + +```js +updateName() { + this.name.setValue('Tina'); +} +``` + +#### 3.3.9 表单控件分组 + +表单中通常会包含几个相互关联的控件。响应式表单提供了两种把多个相关控件分组到同一个输入表单中的方法 + +要将表单组添加到此组件中,请执行以下步骤。 + +1. 创建一个 `FormGroup` 实例。 +2. 把这个 `FormGroup` 模型关联到视图。 +3. 保存表单数据。 + +**创建一个 FormGroup 实例** + +在组件类中创建一个名叫 `loginForm` 的属性,并设置为 `FormGroup` 的一个新实例。要初始化这个 `FormGroup`,请为构造函数提供一个由控件组成的对象,对象中的每个名字都要和表单控件的名字一一对应 + +```js +import { Component } from '@angular/core'; +import { FormGroup, FormControl } from '@angular/forms'; + +@Component({ + selector: 'app-profile-editor', + templateUrl: './profile-editor.component.html', + styleUrls: ['./profile-editor.component.css'] +}) +export class ProfileEditorComponent { + loginForm = new FormGroup({ + userName: new FormControl(''), + password: new FormControl(''), + }); +} + +//模板渲染 +
+ + + + + +
+``` + + + +#### 3.3.10 表单验证 + +​ 表单元素添加`required`关键字表示必填,通过绑定`ngModel`的引用可以拿到到当前组件的信息,通过引用获取到验证的信息 + +```jsx +export class AppComponent { + fromData={ + name:'', + password:'' + }; + + subBtnFUn(obj){ + console.log(obj) + } +} + +
+ 账号: +
+ {{nameInp.valid }} +
+ 密码: +
+ {{pasInp.valid }} +
+ +
+``` + +我们还可以通过 **ngModel** 跟踪修改状态与有效性验证,它使用了三个 CSS 类来更新控件,以便反映当前状态。 + +| 状态 | 为 true 时的类 | 为 false 时的类 | +| :--------------- | :------------- | :-------------- | +| 控件已经被访问过 | `ng-touched` | `ng-untouched` | +| 控件值已经变化 | `ng-dirty` | `ng-pristine` | +| 控件值是有效的 | `ng-valid` | `ng-invalid` | + +#### 3.3.11 自定义表单验证 + +​ 先引入表单的一些内置依赖 + +```js +import { FormGroup, FormBuilder,Validators } from '@angular/forms'; + +//构造函数里注入FormBuilder +constructor(private fb:FormBuilder) { } + +//错误提醒数据 +formErrors = { + 'title': '', + 'content': '' +}; + + +//在组件类的初始化函数里对表单中的元素的校验进行定义,并调用表单的valueChanges方法,检测表单的输入的变化 +ngOnInit():void { + this.taskInfo.isComplete = 1; + this.tasksForm = this.fb.group({ + userName: ['', [Validators.required, + Validators.maxLength(18), + Validators.minLength(6) ] ], + password: ['', [this.passWordVal]], + phone: ['', [Validators.required,this.phoneVal],] + }); + + phoneVal(phone: FormControl): object { + const value = phone.value || ''; + if(!value) return {desc:'请输入手机号'} + const valid = /[0-9]{11}/.test(value); + return valid ? {} :{desc:'联系电话必须是11位数字'} + } + passWordVal(password:FormControl):object{ + const value = password.value || ''; + const valid = value.match(/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,20}$/); + return valid ? {} : {passwordValidator: {desc:'密码至少包含 数字和英文,长度6-20'}} + } +} +``` + +#### 3.3.12 管道 + +​ 管道的作用就是传输。不同的管道具有不同的作用。(其实就是处理数据) + +`angular`中自带的`pipe`函数 + +| 管道 | 功能 | +| ------------- | ------------------------------------------------------------ | +| DatePipe | 日期管道,格式化日期 | +| JsonPipe | 将输入数据对象经过JSON.stringify()方法转换后输出对象的字符串 | +| UpperCasePipe | 将文本所有小写字母转换成大写字母 | +| LowerCasePipe | 将文本所有大写字母转换成小写字母 | +| DecimalPipe | 将数值按照特定的格式显示文本 | +| CurrentcyPipe | 将数值进行货币格式化处理 | +| SlicePipe | 将数组或者字符串裁剪成新子集 | +| PercentPipe | 将数值转百分比格式 | + +`pipe`用法 + +- {{ 输入数据 | 管道 : 管道参数}} (其中‘|’是管道操作符) + +- 链式管道 {{ 输入数据 | date | uppercase}} + +- 管道流通方向自左向右,逐层执行 + + 使用脚手架命令:**ng g p test** + + ```js + import { Pipe, PipeTransform } from '@angular/core'; + + @Pipe({ + name: 'testTitle' + }) + export class TestPipe implements PipeTransform { + + transform(value: unknown, ...args: unknown[]): unknown { + console.log(value) + return 'title'; + } + } + + +

{{ 'Angular' | testTitle }}

+ + ``` + + + +### 3.4 服务 + + angular中,把从组件内抽离出来的代码叫服务,服务的本质就是函数 + + 官方认为组件不应该直接获取或保存数据, 它们应该聚焦于展示数据,而把数据访问的职责委托给某个服务。而服务就充当着数据访问,逻辑处理的功能。把组件和服务区分开,以提高模块性和复用性。通过把组件中和视图有关的功能与其他类型的处理分离开,可以让组件类更加精简、高效。 + +使用命令ng g s xxx创建一个服务,通过**@Injectable()**装饰器标识服务。 + +```js +//导入Injectable装饰器 +import { Injectable } from '@angular/core'; +//使用Injectable装饰器声明服务 +@Injectable({ + //作用域设定,'root'表示默认注入,注入到AppModule里 + providedIn: 'root', +}) +export class TestService { +} + +``` + +组件中如何使用服务呢,必须将服务依赖注入系统、组件或者模块,才能够使用服务。我们可以用**注册提供商**和**根注入器**实现**。** + + 该服务本身是 CLI 创建的一个类,并且加上了 `@Injectable()` 装饰器。默认情况下,该装饰器是用 `providedIn` 属性进行配置的,它会为该服务创建一个提供商。 + + + +### 3.5 依赖注入 + +​ 在这个例子中,`providedIn: 'root'` 指定 Angular 应该在根注入器中提供该服务,从而实现**根注入器**将服务注入,它就在整个应用程序中可用了**。** + +**providedIn**: + +​ 'root' :注入到AppModule,提供该服务,所有子组件都可以使用(推荐) + +​ null : 不设定服务作用域(不推荐) + +​ 组件名:只作用于该组件(懒加载模式) + +```js + +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class TestService { +} + + +import { Component, OnInit } from '@angular/core'; +import {HeroService} from '../hero.service' + +@Component({ + selector: 'app-test', + templateUrl: './test.component.html', + styleUrls: ['./test.component.scss'], +}) +export class TestComponent implements OnInit { + + constructor(private heroService:HeroService) { } + + ngOnInit(): void { + console.log(this.heroService.getHeroList()) + } +} +``` + +​ 也可以使用 `@Component` 或 `@Directive` 内部的 `providers: []`,为特定的组件子树提供服务,这也将导致创建多个服务实例(每个组件使用一个服务实例) + +```js + +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class TestService { +} + + +import { Component, OnInit } from '@angular/core'; +import {HeroService} from '../hero.service' + +@Component({ + selector: 'app-test', + templateUrl: './test.component.html', + styleUrls: ['./test.component.scss'], + providers: [HeroService] +}) +export class TestComponent implements OnInit { + + constructor(private heroService:HeroService) { } + + ngOnInit(): void { + console.log(this.heroService.getHeroList()) + } +} +``` + + + +### 3.6 路由 + +​ 路由就是连接组件的筋络,它也是树形结构的.有了它,就可以在angular中实现路径的导航模式 + +可以把路由看成是一组规则,它决定了url的变化对应着哪一种状态,具体表现就是不同视图的切换 + +在angular中,路由是非常重要的组成部分, 组件的实例化与销毁,模块的加载,组件的某些生命周期钩子的发起,都是与它有关 + +#### 3.6.1 路由基本使用 + +**路由器**是一个调度中心,它是一套规则的列表,能够查询当前URL对应的规则,并呈现出相应的视图. + +**路由**是列表里面的一个规则,即路由定义,它有很多功能字段: + +- **path**字段,表示该路由中的URL路径部分 +- **Component**字段,表示与该路由相关联的组件 + +每个带路由的Angular应用都有一个路由器服务的单例对象,通过路由定义的列表进行配置后使用。 + +```js +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import {HomeComponent} from './home/home.component' + +const routes: Routes = [ + {path:'home',component:HomeComponent} +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] +}) +export class AppRoutingModule { } + +``` + +```jsx +//路由导航 +home + hello + +//组件渲染输出 + +``` + +上述具体的工作流程,可以举例简单描述为: + +- 当浏览器地址栏的URL变化时,路径部分`/home`满足了列表中path为"**home**"的这个路由定义,激活对应**HomeComponent**的实例,显示它的视图 +- 当应用程序请求导航到路径`/hello`时,符合了另外的规则,激活对应视图且展示内容,并将该路径更新到浏览器地址栏和历史 + +#### 3.6.2 路由嵌套 + +​ 父子路由嵌套配置: + +```js +const routes: Routes = [ + {path:'home', + component:HomeComponent, + children:[ + {path:'hello',component:HelloComponent} + ] + }, +]; +``` + +​ 在**home Component**内这是`router-outlet`路由出口,即可在**home** 路由内渲染子级路由 + +```jsx +//home template +

home Component

+hello + +``` + +​ 在非`home Component`内跳转到`/home/hello`路由需要写全路径 + +```jsx +//app template +

app Component

+hello + +``` + +​ + +#### 3.6.3 路由传参 + +- **query** + + 在a标签上添加一个参数queryParams,并通过`this.routerinfo.snapshot.queryParams`获取参数 + + ```jsx + hello + ``` + + ```js + import {ActivatedRoute} from '@angular/router'; + constructor(private routerinfo:ActivatedRoute) { } + + ngOnInit() { + //id为参数名字 + this.id=this.routerinfo.snapshot.queryParams["id"] + } + ``` + + + +- **params** + + 修改路由配置文件`path`,路由导航`a`标签`routerLink`后面数组的第二个参数为传递的值 + + 并且通过`subscribe`请阅的方式获取`name`参数 + + ```js + { + path: 'hello/:name', + component:HelloComponent, + }, + ``` + + ```jsx + //我们在后面添加/:name此时name即为传递的参数名字 + //a标签设置如下 + hello + ``` + + ```js + ngOnInit() { + this.routerinfo.params.subscribe((params:Params)=>{ + this.name=params['name'] + }) + } + ``` + diff --git a/notes/data-structures-and-algorithms/LeetCode.md b/notes/data-structures-and-algorithms/LeetCode.md new file mode 100644 index 00000000..a98174fb --- /dev/null +++ b/notes/data-structures-and-algorithms/LeetCode.md @@ -0,0 +1 @@ +# LeetCode \ No newline at end of file diff --git a/notes/data-structures-and-algorithms/README.md b/notes/data-structures-and-algorithms/README.md new file mode 100644 index 00000000..e824a536 --- /dev/null +++ b/notes/data-structures-and-algorithms/README.md @@ -0,0 +1,51 @@ +# 数据结构与算法 + +> 欢迎来到数据结构与算法的世界,带你从基础数据结构层层深入。本章以 Java 代码为例,结合 LeetCode 题解示例进行数据结构学习。 + + + +## 学习目录 + +| 部分 | 章节 | 概要 | +| :--- | :--------------------------- | :--------------------------------------------------- | +| Ⅰ | [数据结构](数据结构.md) | 线性表、栈和队列、树和二叉树等经典数据结构 | +| Ⅱ | [算法思想](算法思想.md) | 排序算法、动态规划、递归、回溯法、贪心算法等经典算法 | +| Ⅲ | [LeetCode 题解](LeetCode.md) | LeetCode 热题 HOT 100 | + + + +## 在线刷题 + +- 题库 - 力扣 (LeetCode) 全球极客挚爱的技术成长平台 + + https://leetcode-cn.com/problemset/all + +- 牛客网 - 找工作神器|笔试题库|面试经验|实习招聘内推,求职就业一站解决 + + https://www.nowcoder.com/ + + + +## 在线工具 + +- VisuAlgo - visualising data structures and algorithms through animation + + https://visualgo.net/en + +- Data Structure Visualization + + https://www.cs.usfca.edu/~galles/visualization/Algorithms.html + + + +## 学习课程 + +| 课程 | 链接 | 代码 | 推荐 | +| ------------------------------------------------------ | ------------------------------------------------------- | ------------------------------------------------------------ | ----------------------------------------- | +| 【慕课网】玩转数据结构 从入门到进阶(Java) | [官网](https://coding.imooc.com/class/chapter/207.html) | [代码](https://github.com/liuyubobobo/Play‑with‑Data‑Structures) | 数据结构从底层到实现,浅显易懂 | +| 【慕课网】算法与数据结构体系课(Java)
| [官网](https://class.imooc.com/datastructure#Anchor) | [代码](https://github.com/frank‑lam/Algorithm) | 《玩转数据结构 从入门到进阶》 - 升级版 | +| 【慕课网】程序员的内功修炼,学好算法与数据结构(C++) | [官网](https://coding.imooc.com/class/chapter/71.html) | [代码](https://github.com/liuyubobobo/Play‑with‑Algorithms) | 程序员的内功修炼,强烈推荐 | +| 【慕课网】玩转算法面试 LeetCode 题库(C++) | [官网](https://coding.imooc.com/class/chapter/82.html) | [代码](https://github.com/liuyubobobo/Play‑with‑Algorithm‑Interview) | LeetCode 刷题入门,强烈推荐 | +| 【极客时间】覃超:算法面试通关40讲 | [官网](https://time.geekbang.org/course/intro/130) | / | 市面上比较新的课程,推荐 | +| 【慕课网】刘宇波:看得见的算法 7个经典应用诠释算法精髓 | [官网](https://coding.imooc.com/class/chapter/138.html) | / | 通过7款经典好玩游戏,真正将算法用于实际开 | + diff --git a/notes/data-structures-and-algorithms/assets/image-20210821164803357.png b/notes/data-structures-and-algorithms/assets/image-20210821164803357.png new file mode 100644 index 00000000..1b18aee3 Binary files /dev/null and b/notes/data-structures-and-algorithms/assets/image-20210821164803357.png differ diff --git a/notes/data-structures-and-algorithms/assets/image-20210821164814332.png b/notes/data-structures-and-algorithms/assets/image-20210821164814332.png new file mode 100644 index 00000000..1b18aee3 Binary files /dev/null and b/notes/data-structures-and-algorithms/assets/image-20210821164814332.png differ diff --git a/notes/data-structures-and-algorithms/assets/image-20210821164819266.png b/notes/data-structures-and-algorithms/assets/image-20210821164819266.png new file mode 100644 index 00000000..1b18aee3 Binary files /dev/null and b/notes/data-structures-and-algorithms/assets/image-20210821164819266.png differ diff --git a/notes/data-structures-and-algorithms/assets/image-20210821172111765.png b/notes/data-structures-and-algorithms/assets/image-20210821172111765.png new file mode 100644 index 00000000..5ab3b50e Binary files /dev/null and b/notes/data-structures-and-algorithms/assets/image-20210821172111765.png differ diff --git a/notes/data-structures-and-algorithms/assets/logo7.svg b/notes/data-structures-and-algorithms/assets/logo7.svg new file mode 100644 index 00000000..bfc750fb --- /dev/null +++ b/notes/data-structures-and-algorithms/assets/logo7.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/notes/data-structures-and-algorithms/assets/logo8.svg b/notes/data-structures-and-algorithms/assets/logo8.svg new file mode 100644 index 00000000..49e3c852 --- /dev/null +++ b/notes/data-structures-and-algorithms/assets/logo8.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git "a/notes/data-structures-and-algorithms/leetcode/Leetcode 307. \345\214\272\345\237\237\345\222\214\346\243\200\347\264\242 - \346\225\260\347\273\204\345\217\257\344\277\256\346\224\271.md" "b/notes/data-structures-and-algorithms/leetcode/Leetcode 307. \345\214\272\345\237\237\345\222\214\346\243\200\347\264\242 - \346\225\260\347\273\204\345\217\257\344\277\256\346\224\271.md" new file mode 100644 index 00000000..78b304a4 --- /dev/null +++ "b/notes/data-structures-and-algorithms/leetcode/Leetcode 307. \345\214\272\345\237\237\345\222\214\346\243\200\347\264\242 - \346\225\260\347\273\204\345\217\257\344\277\256\346\224\271.md" @@ -0,0 +1,213 @@ +https://leetcode-cn.com/problems/range-sum-query-mutable + +## 307. 区域和检索 - 数组可修改 + +给你一个数组 `nums` ,请你完成两类查询,其中一类查询要求更新数组下标对应的值,另一类查询要求返回数组中某个范围内元素的总和。 + +实现 `NumArray` 类: + +- `NumArray(int[] nums)` 用整数数组 `nums` 初始化对象 +- `void update(int index, int val)` 将 `nums[index]` 的值更新为 `val` +- `int sumRange(int left, int right)` 返回子数组 `nums[left, right]` 的总和(即,`nums[left] + nums[left + 1], ..., nums[right]`) + + + +**示例:** + +``` +输入: +["NumArray", "sumRange", "update", "sumRange"] +[[[1, 3, 5]], [0, 2], [1, 2], [0, 2]] +输出: +[null, 9, null, 8] + +解释: +NumArray numArray = new NumArray([1, 3, 5]); +numArray.sumRange(0, 2); // 返回 9 ,sum([1,3,5]) = 9 +numArray.update(1, 2); // nums = [1,2,5] +numArray.sumRange(0, 2); // 返回 8 ,sum([1,2,5]) = 8 +``` + + + +**提示:** + +- `1 <= nums.length <= 3 * 104` +- `-100 <= nums[i] <= 100` +- `0 <= index < nums.length` +- `-100 <= val <= 100` +- `0 <= left <= right < nums.length` +- 最多调用 `3 * 104` 次 `update` 和 `sumRange` 方法 + + + +**提交代码:** + +```java +class NumArray { + public class SegmentTree { + + private E[] tree; + private E[] data; + private Merger merger; + + public SegmentTree(E[] arr, Merger merger){ + + this.merger = merger; + + data = (E[])new Object[arr.length]; + for(int i = 0 ; i < arr.length ; i ++) + data[i] = arr[i]; + + tree = (E[])new Object[4 * arr.length]; + buildSegmentTree(0, 0, arr.length - 1); + } + + // 在treeIndex的位置创建表示区间[l...r]的线段树 + private void buildSegmentTree(int treeIndex, int l, int r){ + + if(l == r){ + tree[treeIndex] = data[l]; + return; + } + + int leftTreeIndex = leftChild(treeIndex); + int rightTreeIndex = rightChild(treeIndex); + + // int mid = (l + r) / 2; + int mid = l + (r - l) / 2; + buildSegmentTree(leftTreeIndex, l, mid); + buildSegmentTree(rightTreeIndex, mid + 1, r); + + tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]); + } + + public int getSize(){ + return data.length; + } + + public E get(int index){ + if(index < 0 || index >= data.length) + throw new IllegalArgumentException("Index is illegal."); + return data[index]; + } + + // 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引 + private int leftChild(int index){ + return 2*index + 1; + } + + // 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引 + private int rightChild(int index){ + return 2*index + 2; + } + + // 返回区间[queryL, queryR]的值 + public E query(int queryL, int queryR){ + + if(queryL < 0 || queryL >= data.length || + queryR < 0 || queryR >= data.length || queryL > queryR) + throw new IllegalArgumentException("Index is illegal."); + + return query(0, 0, data.length - 1, queryL, queryR); + } + + // 在以treeIndex为根的线段树中[l...r]的范围里,搜索区间[queryL...queryR]的值 + private E query(int treeIndex, int l, int r, int queryL, int queryR){ + + if(l == queryL && r == queryR) + return tree[treeIndex]; + + int mid = l + (r - l) / 2; + // treeIndex的节点分为[l...mid]和[mid+1...r]两部分 + + int leftTreeIndex = leftChild(treeIndex); + int rightTreeIndex = rightChild(treeIndex); + if(queryL >= mid + 1) + return query(rightTreeIndex, mid + 1, r, queryL, queryR); + else if(queryR <= mid) + return query(leftTreeIndex, l, mid, queryL, queryR); + + E leftResult = query(leftTreeIndex, l, mid, queryL, mid); + E rightResult = query(rightTreeIndex, mid + 1, r, mid + 1, queryR); + return merger.merge(leftResult, rightResult); + } + + // 将index位置的值,更新为e + public void set(int index, E e){ + + if(index < 0 || index >= data.length) + throw new IllegalArgumentException("Index is illegal"); + + data[index] = e; + set(0, 0, data.length - 1, index, e); + } + + // 在以treeIndex为根的线段树中更新index的值为e + private void set(int treeIndex, int l, int r, int index, E e){ + + if(l == r){ + tree[treeIndex] = e; + return; + } + + int mid = l + (r - l) / 2; + // treeIndex的节点分为[l...mid]和[mid+1...r]两部分 + + int leftTreeIndex = leftChild(treeIndex); + int rightTreeIndex = rightChild(treeIndex); + if(index >= mid + 1) + set(rightTreeIndex, mid + 1, r, index, e); + else // index <= mid + set(leftTreeIndex, l, mid, index, e); + + tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]); + } + + @Override + public String toString(){ + StringBuilder res = new StringBuilder(); + res.append('['); + for(int i = 0 ; i < tree.length ; i ++){ + if(tree[i] != null) + res.append(tree[i]); + else + res.append("null"); + + if(i != tree.length - 1) + res.append(", "); + } + res.append(']'); + return res.toString(); + } + } + + public interface Merger { + E merge(E a, E b); + } + + private SegmentTree segTree; + + public NumArray(int[] nums) { + + if(nums.length != 0){ + Integer[] data = new Integer[nums.length]; + for(int i = 0 ; i < nums.length ; i ++) + data[i] = nums[i]; + segTree = new SegmentTree<>(data, (a, b) -> a + b); + } + } + + public void update(int i, int val) { + if(segTree == null) + throw new IllegalArgumentException("Error"); + segTree.set(i, val); + } + + public int sumRange(int i, int j) { + if(segTree == null) + throw new IllegalArgumentException("Error"); + return segTree.query(i, j); + } +} +``` \ No newline at end of file diff --git "a/notes/data-structures-and-algorithms/\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/notes/data-structures-and-algorithms/\346\225\260\346\215\256\347\273\223\346\236\204.md" new file mode 100644 index 00000000..b3ffc0cd --- /dev/null +++ "b/notes/data-structures-and-algorithms/\346\225\260\346\215\256\347\273\223\346\236\204.md" @@ -0,0 +1,286 @@ +# 数据结构 + +## 一、基础数据结构 + +### 字符串 + +- 标准库,解析,匹配等 + +### 线性表 + +- 数组 + +- 动态数组 + +### 栈和队列 + +### 链表 + +### 哈希表 + + + +## 二、高级数据结构 + +### 1. 线段树 + +线段树(segment tree),顾名思义, 是用来存放给定区间(segment, or interval)内对应信息的一种数据结构。与树状数组(binary indexed tree)相似,线段树也用来处理数组相应的区间查询(range query)和元素更新(update)操作。与树状数组不同的是,线段树不止可以适用于区间求和的查询,也可以进行区间最大值,区间最小值(Range Minimum/Maximum Query problem)或者区间异或值的查询。 + +对应于树状数组,线段树进行更新(update)的操作为 O(logn),进行区间查询(range query)的操作也为 O(logn)。 + +![image-20210821164803357](assets/image-20210821164803357.png) + +在线可视化:https://visualgo.net/en/segmenttree + + + +#### 303. 区域和检索 - 数组不可变 (LeetCode) + + +https://leetcode-cn.com/problems/range-sum-query-immutable + +```java +class NumArray { + private int[] data; + + public NumArray(int[] nums) { + this.data = new int[nums.length]; + for (int i = 0; i < nums.length; i++) { + this.data[i] = nums[i]; + } + } + + public void update(int index, int val) { + this.data[index] = val; + } + + public int sumRange(int left, int right) { + int sum = 0; + for (int i = left; i <= right; i++) { + sum += this.data[i]; + } + return sum; + } +} +``` + + + +#### 307. 区域和检索 - 数组可修改 (LeetCode) + +https://leetcode-cn.com/problems/range-sum-query-mutable + +```java + +class NumArray { + public class SegmentTree { + private E[] tree; + private E[] data; + private Merger merger; + + public SegmentTree(E[] arr, Merger merger){ + + this.merger = merger; + + data = (E[])new Object[arr.length]; + for(int i = 0 ; i < arr.length ; i ++) + data[i] = arr[i]; + + tree = (E[])new Object[4 * arr.length]; + buildSegmentTree(0, 0, arr.length - 1); + } + + // 在treeIndex的位置创建表示区间[l...r]的线段树 + private void buildSegmentTree(int treeIndex, int l, int r){ + + if(l == r){ + tree[treeIndex] = data[l]; + return; + } + + int leftTreeIndex = leftChild(treeIndex); + int rightTreeIndex = rightChild(treeIndex); + + // int mid = (l + r) / 2; + int mid = l + (r - l) / 2; + buildSegmentTree(leftTreeIndex, l, mid); + buildSegmentTree(rightTreeIndex, mid + 1, r); + + tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]); + } + + public int getSize(){ + return data.length; + } + + public E get(int index){ + if(index < 0 || index >= data.length) + throw new IllegalArgumentException("Index is illegal."); + return data[index]; + } + + // 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引 + private int leftChild(int index){ + return 2*index + 1; + } + + // 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引 + private int rightChild(int index){ + return 2*index + 2; + } + + // 返回区间[queryL, queryR]的值 + public E query(int queryL, int queryR){ + + if(queryL < 0 || queryL >= data.length || + queryR < 0 || queryR >= data.length || queryL > queryR) + throw new IllegalArgumentException("Index is illegal."); + + return query(0, 0, data.length - 1, queryL, queryR); + } + + // 在以treeIndex为根的线段树中[l...r]的范围里,搜索区间[queryL...queryR]的值 + private E query(int treeIndex, int l, int r, int queryL, int queryR){ + + if(l == queryL && r == queryR) + return tree[treeIndex]; + + int mid = l + (r - l) / 2; + // treeIndex的节点分为[l...mid]和[mid+1...r]两部分 + + int leftTreeIndex = leftChild(treeIndex); + int rightTreeIndex = rightChild(treeIndex); + if(queryL >= mid + 1) + return query(rightTreeIndex, mid + 1, r, queryL, queryR); + else if(queryR <= mid) + return query(leftTreeIndex, l, mid, queryL, queryR); + + E leftResult = query(leftTreeIndex, l, mid, queryL, mid); + E rightResult = query(rightTreeIndex, mid + 1, r, mid + 1, queryR); + return merger.merge(leftResult, rightResult); + } + + // 将index位置的值,更新为e + public void set(int index, E e){ + + if(index < 0 || index >= data.length) + throw new IllegalArgumentException("Index is illegal"); + + data[index] = e; + set(0, 0, data.length - 1, index, e); + } + + // 在以treeIndex为根的线段树中更新index的值为e + private void set(int treeIndex, int l, int r, int index, E e){ + + if(l == r){ + tree[treeIndex] = e; + return; + } + + int mid = l + (r - l) / 2; + // treeIndex的节点分为[l...mid]和[mid+1...r]两部分 + + int leftTreeIndex = leftChild(treeIndex); + int rightTreeIndex = rightChild(treeIndex); + if(index >= mid + 1) + set(rightTreeIndex, mid + 1, r, index, e); + else // index <= mid + set(leftTreeIndex, l, mid, index, e); + + tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]); + } + + @Override + public String toString(){ + StringBuilder res = new StringBuilder(); + res.append('['); + for(int i = 0 ; i < tree.length ; i ++){ + if(tree[i] != null) + res.append(tree[i]); + else + res.append("null"); + + if(i != tree.length - 1) + res.append(", "); + } + res.append(']'); + return res.toString(); + } + } + + public interface Merger { + E merge(E a, E b); + } + + private SegmentTree segTree; + + public NumArray(int[] nums) { + + if(nums.length != 0){ + Integer[] data = new Integer[nums.length]; + for(int i = 0 ; i < nums.length ; i ++) + data[i] = nums[i]; + segTree = new SegmentTree<>(data, (a, b) -> a + b); + } + } + + public void update(int i, int val) { + if(segTree == null) + throw new IllegalArgumentException("Error"); + segTree.set(i, val); + } + + public int sumRange(int i, int j) { + if(segTree == null) + throw new IllegalArgumentException("Error"); + return segTree.query(i, j); + } +} +``` + + + +### 2. Trie + +https://blog.csdn.net/jt102605/article/details/84258314 + +https://blog.csdn.net/longgeqiaojie304/article/details/106316103 + +发生在微软的一个真实案例: + +在一个古老的手持设备中实现一个通讯录功能,但是当时的手持设备的芯片运算能力是非常低的,所以他们发现当通讯录中记录的条目非常多的时候,搜索通讯录中的内容是非常慢的。但是这个问题是被微软的一个实习生解决了。其实他解决的方式非常简单,他就是使用了这种Trie数据结构来解决的。 + + +![image-20210821172111765](assets/image-20210821172111765.png) + +### 树和二叉树 + +- 二叉树 + +- 二分搜索树 + +- AVL 树 + +- B 和 B+ + +- 二分查找法 + - Leetcode 704. + +- 红黑树 + + + +### 集合和映射 + + + +### 优先队列和堆 + + + + + +### Trie + +### 并查集 diff --git "a/notes/data-structures-and-algorithms/\347\256\227\346\263\225\346\200\235\346\203\263.md" "b/notes/data-structures-and-algorithms/\347\256\227\346\263\225\346\200\235\346\203\263.md" new file mode 100644 index 00000000..28a34932 --- /dev/null +++ "b/notes/data-structures-and-algorithms/\347\256\227\346\263\225\346\200\235\346\203\263.md" @@ -0,0 +1,730 @@ +# 算法思想 + +## 一、排序算法 + +### 1. 选择排序(Selection Sort) + +选择出数组中的最小元素,将它与数组的第一个元素交换位置。再从剩下的元素中选择出最小的元素,将它与数组的第二个元素交换位置。不断进行这样的操作,直到将整个数组排序。 + +![](https://images2017.cnblogs.com/blog/849589/201710/849589-20171015224719590-1433219824.gif) + +**代码实现** + +```java +public static void sort(int[] arr) { + for (int i = 0; i < arr.length; i++) { + // 寻找[i, n)区间里的最小值的索引 + int minIndex = i; + for (int j = i + 1; j < arr.length; j++) { + if(arr[minIndex] > arr[j]){ + minIndex = j; + } + } + swap( arr , i , minIndex); + } +} + +private static void swap(int[] arr, int i, int j) { + int t = arr[i]; + arr[i] = arr[j]; + arr[j] = t; +} +``` + +**算法分析** + +表现最稳定的排序算法之一,因为无论什么数据进去都是O(n2)的时间复杂度,所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。理论上讲,选择排序可能也是平时排序一般人想到的最多的排序方法了吧。 + + + +### 2. 插入排序(Insertion Sort) + +插入排序从左到右进行,每次都将当前元素插入到左侧已经排序的数组中,使得插入之后左部数组依然有序。 + +第 j 元素是通过不断向左比较并交换来实现插入过程:当第 j 元素小于第 j - 1 元素,就将它们的位置交换,然后令 j 指针向左移动一个位置,不断进行以上操作。 + +![](https://images2017.cnblogs.com/blog/849589/201710/849589-20171015225645277-1151100000.gif) + +**代码实现** + +```java +public static void sort(int[] arr) { + for (int i = 0; i < arr.length; i++) { + // 选择 arr[i...n) 中的最小值 + int minIndex = i; + for (int j = i + 1; j < arr.length; j++) { + if (arr[minIndex] > arr[j]) { + minIndex = j; + } + } + swap(arr, i, minIndex); + } +} + +// 改进版插入排序(减少了数组元素的操作次数) +public static void better_sort(int[] arr) { + for (int i = 0; i < arr.length; i++) { + int e = arr[i]; + int j = i; + for (; j > 0; j--) { + if (e < arr[j - 1]) + arr[j] = arr[j - 1]; + else + break; + } + arr[j] = e; + } +} + +private static void swap(int[] arr, int i, int j) { + int t = arr[i]; + arr[i] = arr[j]; + arr[j] = t; +} +``` + +**算法分析** + +插入排序在实现上,通常采用 in-place 排序(即只需用到 O(1) 的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。 + + + +### 3. 冒泡排序(Bubble Sort) + +通过从左到右不断交换相邻逆序的相邻元素,在一轮的交换之后,可以让未排序的元素上浮到右侧。 + +在一轮循环中,如果没有发生交换,就说明数组已经是有序的,此时可以直接退出。 + +![](https://images2017.cnblogs.com/blog/849589/201710/849589-20171015223238449-2146169197.gif) + +**代码实现** + +```java +private static void sort(int[] arr) { + for (int i = arr.length - 1; i > 0; i--) { // 从最后一位开始确定 + boolean swapped = false; + for (int j = 0; j < i; j++) { + if(arr[j] > arr[j+1]){ + swapped = true; + swap(arr,j,j+1); + } + } + if(!swapped) + return; + } +} + +private static void swap(int[] arr, int i, int j) { + int t = arr[i]; + arr[i] = arr[j]; + arr[j] = t; +} +``` + + + +### 4. 希尔排序(Shell Sort) + +1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫**缩小增量排序**。 + + + +**算法描述** + +先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述: + +- 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1; +- 按增量序列个数k,对序列进行k 趟排序; +- 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。 + +![](https://images2018.cnblogs.com/blog/849589/201803/849589-20180331170017421-364506073.gif) + +**代码实现** + +```java +// 希尔排序 +public static void sort(int[] arr) { + int n = arr.length; + for (int h = n / 2; h > 0; h = h / 2) { + // 内部是一个插入排序 + for (int i = 0; i < n; i = i + h) { + + int e = arr[i]; + int j = i; + for (; j > 0; j = j - h) { + if (e < arr[j - h]) + arr[j] = arr[j - h]; + else + break; + } + arr[j] = e; + } + } +} + + +// 希尔排序2 +public static void sort2(int[] arr) { + int n = arr.length; + // 计算 increment sequence: 1, 4, 13, 40, 121, 364, 1093... + int h = 1; + while (h < n / 3) h = 3 * h + 1; + + System.out.println(h); + + while (h >= 1) { + // h-sort the array + for (int i = h; i < n; i++) { + + // 对 arr[i], arr[i-h], arr[i-2*h], arr[i-3*h]... 使用插入排序 + int e = arr[i]; + int j = i; + for (; j >= h && e < arr[j - h]; j -= h) + arr[j] = arr[j - h]; + arr[j] = e; + } + + h /= 3; + } +} +``` + +**算法分析** + +对于大规模的数组,插入排序很慢,因为它只能交换相邻的元素,每次只能将逆序数量减少 1。 + +希尔排序的出现就是为了改进插入排序的这种局限性,它通过交换不相邻的元素,每次可以将逆序数量减少大于 1。 + +希尔排序使用插入排序对间隔 h 的序列进行排序。通过不断减小 h,最后令 h=1,就可以使得整个数组是有序的。 + + + +### 5. 归并排序(Merge Sort) + +归并排序的思想是将数组分成两部分,分别进行排序,然后归并起来。把长度为n的输入序列分成两个长度为n/2的子序列;对这两个子序列分别采用归并排序;将两个排序好的子序列合并成一个最终的排序序列。 + +![](https://images2017.cnblogs.com/blog/849589/201710/849589-20171015230557043-37375010.gif) + +**代码实现** + +> 1.归并方法 +> +> 归并方法将数组中两个已经排序的部分归并成一个。 + +```java +private static void sort(int[] arr) { + __MergeSort(arr, 0, arr.length - 1); +} + +private static void __MergeSort(int[] arr, int l, int r) { + if (l >= r) + return; + int mid = (l + r) / 2; + __MergeSort(arr, l, mid); + __MergeSort(arr, mid + 1, r); + merge(arr, l, mid, r); +} + +// 将arr[l...mid]和arr[mid+1...r]两部分进行归并 +private static void merge(int[] arr, int l, int mid, int r) { + int[] aux = Arrays.copyOfRange(arr, l, r + 1); + + // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1 + int i = l, j = mid + 1; + for (int k = l; k <= r; k++) { + if (i > mid) { // 如果左半部分元素已经全部处理完毕 + arr[k] = aux[j - l]; + j++; + } else if (j > r) { // 如果右半部分元素已经全部处理完毕 + arr[k] = aux[i - l]; + i++; + } else if (aux[i - l] < aux[j - l]) { // 左半部分所指元素 < 右半部分所指元素 + arr[k] = aux[i - l]; + i++; + } else { // 左半部分所指元素 >= 右半部分所指元素 + arr[k] = aux[j - l]; + j++; + } + } +} +``` + +> 2.自底向上归并排序 + +```java +private static void sort(int[] arr) { + int N = arr.length; + int[] aux = new int[N]; + for (int sz = 1; sz < N; sz += sz) + for (int i = 0; i + sz < N; i += sz + sz) + merge(arr, i, i + sz - 1, Math.min(i + sz + sz - 1, N - 1)); +} +``` + + + +### 6. 快速排序(Quick Sort) + +快速排序可以说是20世纪最伟大的算法之一了。相信都有所耳闻,它的速度也正如它的名字那样,是一个非常快的算法了。当然它也后期经过了不断的改进和优化,才被公认为是一个值得信任的非常优秀的算法。 + +![](https://images2017.cnblogs.com/blog/849589/201710/849589-20171015230936371-1413523412.gif) + +**代码实现** + +#### 1. 普通快速排序 + +```java +// 递归使用快速排序,对arr[l...r]的范围进行排序 +public static void QuickSort(int[] arr,int l,int r){ + if(l>=r) + return; + int p = partition(arr,l,r); + QuickSort(arr,l,p-1); + QuickSort(arr,p+1,r); +} + +// 将数组通过p分割成两部分 +// 对arr[l...r]部分进行partition操作 +// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p] +public static int partition(int[] arr, int l, int r) { + swap(arr, l, (int) (Math.random() % (r - l + 1)) + l); // 加入这一行变成随机快速排序 + + int v = arr[l]; + int j = l; + for(int i = j +1;i<=r;i++){ + if(arr[i] < v){ + j++; + swap(arr,i,j); + } + } + swap(arr,l,j); + return j; +} + +public static void swap(int[] arr,int i,int j) { + int temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; +} +``` + +快速排序是原地排序,不需要辅助数组,但是递归调用需要辅助栈。 + +快速排序最好的情况下是每次都正好能将数组对半分,这样递归调用次数才是最少的。这种情况下比较次数为 CN=2CN/2+N,复杂度为 O(NlogN)。 + +最坏的情况下,第一次从最小的元素切分,第二次从第二小的元素切分,如此这般。因此最坏的情况下需要比较 N2/2。为了防止数组最开始就是有序的,在进行快速排序时需要随机打乱数组。 + + + +#### 2. 双路快速排序 + +若果数组中含有大量重复的元素,则partition很可能把数组划分成两个及其不平衡的两部分,时间复杂度退化成O(n²)。这时候应该把小于v和大于v放在数组两端。 + +![](../pics/partition2.jpg) + +```java +// 双路快速排序的partition +// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p] +private static int partition(int[] arr, int l, int r) { + + // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot + // swap(arr, l, (int) (Math.random() % (r - l + 1)) + l); + + int v = arr[l]; + + // arr[l+1...i) <= v; arr(j...r] >= v + int i = l + 1, j = r; + while (true) { + // 注意这里的边界, arr[i].compareTo(v) < 0, 不能是arr[i].compareTo(v) <= 0 + // 思考一下为什么? + while (i <= r && arr[i] < v) + i++; + + // 注意这里的边界, arr[j].compareTo(v) > 0, 不能是arr[j].compareTo(v) >= 0 + // 思考一下为什么? + while (j >= l + 1 && arr[j] > v) + j--; + + // 对于上面的两个边界的设定, 有的同学在课程的问答区有很好的回答:) + // 大家可以参考: http://coding.imooc.com/learn/questiondetail/4920.html + if (i > j) + break; + + swap(arr, i, j); + i++; + j--; + } + + swap(arr, l, j); + + return j; +} + +// 递归使用快速排序,对arr[l...r]的范围进行排序 +private static void QuickSort2Ways(int[] arr, int l, int r) { + // 对于小规模数组, 使用插入排序 + if (l >= r) return; + int p = partition(arr, l, r); + QuickSort2Ways(arr, l, p - 1); + QuickSort2Ways(arr, p + 1, r); +} +``` + +#### 3. 三路快速排序 + +数组分成三个部分,大于v 等于v 小于v + +在具有大量重复键值对的情况下使用三路快排 + +![](../pics/partition3.jpg) + + + +```java +// 递归使用快速排序,对arr[l...r]的范围进行排序 +private static void QuickSort3Ways(int[] arr, int l, int r){ + + // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot + swap( arr, l, (int)(Math.random()*(r-l+1)) + l ); + + int v = arr[l]; + + int lt = l; // arr[l+1...lt] < v + int gt = r + 1; // arr[gt...r] > v + int i = l+1; // arr[lt+1...i) == v + while( i < gt ){ + if( arr[i] < v){ + swap( arr, i, lt+1); + i ++; + lt ++; + } + else if( arr[i] > v ){ + swap( arr, i, gt-1); + gt --; + } + else{ // arr[i] == v + i ++; + } + } + swap( arr, l, lt ); + + QuickSort3Ways(arr, l, lt-1); + QuickSort3Ways(arr, gt, r); +} +``` + + + +### 7. 堆排序(Heap Sort) + +#### 1. 堆 + +堆的某个节点的值总是大于等于子节点的值,并且堆是一颗完全二叉树。 + +堆可以用数组来表示,因为堆是完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2,而它的两个子节点的位置分别为 2k 和 2k+1。这里不使用数组索引为 0 的位置,是为了更清晰地描述节点的位置关系。 + +![](../pics/heap.png) + +#### 2. 上浮和下沉 + +在堆中,当一个节点比父节点大,那么需要交换这个两个节点。交换后还可能比它新的父节点大,因此需要不断地进行比较和交换操作,把这种操作称为**上浮(ShiftUp)**。 + +![](../pics/shiftup_heap.png) + +```java +private void shiftUp(int k){ + while( k > 1 && data[k/2] < data[k])){ + swap(k, k/2); + k /= 2; + } +} +``` + + + +类似地,当一个节点比子节点来得小,也需要不断地向下进行比较和交换操作,把这种操作称为**下沉(Shift Down)**。一个节点如果有两个子节点,应当与两个子节点中最大那么节点进行交换。 + +![](../pics/shiftdown_heap.png) + +```java +private void shiftDown(int k){ + while( 2*k <= count ){ // 当前结点有左孩子 + int j = 2*k; // 在此轮循环中,data[k]和data[j]交换位置 + if( j+1 <= count && data[j+1] > data[j] ) + j ++; + // data[j] 是 data[2*k]和data[2*k+1]中的最大值 + + if( data[k] >= data[j] ) + break; + swap(k, j); + k = j; + } +} +``` + +#### 3.插入元素 + +将新元素放到数组末尾,然后上浮到合适的位置。 + +```java +// 向最大堆中插入一个新的元素 item +public void insert(Item item){ + assert count + 1 <= capacity; + data[count+1] = item; + count ++; + shiftUp(count); +} +``` + +#### 4. 删除最大元素 + +```java +// 从最大堆中取出堆顶元素, 即堆中所存储的最大数据 +public Item extractMax(){ + assert count > 0; + Item ret = data[1]; + + swap( 1 , count ); + count --; + shiftDown(1); + return ret; +} +``` + +#### 5. 堆排序 + +由于堆可以很容易得到最大的元素并删除它,不断地进行这种操作可以得到一个递减序列。如果把最大元素和当前堆中数组的最后一个元素交换位置,并且不删除它,那么就可以得到一个从尾到头的递减序列,从正向来看就是一个递增序列。因此很容易使用堆来进行排序。并且堆排序是原地排序,不占用额外空间。 + +```java +// 不使用一个额外的最大堆, 直接在原数组上进行原地的堆排序 +public class HeapSort { + + // 对整个arr数组使用HeapSort1排序 + // HeapSort1, 将所有的元素依次添加到堆中, 在将所有元素从堆中依次取出来, 即完成了排序 + // 无论是创建堆的过程, 还是从堆中依次取出元素的过程, 时间复杂度均为O(nlogn) + // 整个堆排序的整体时间复杂度为O(nlogn) + public static void sort1(Comparable[] arr){ + + int n = arr.length; + MaxHeap maxHeap = new MaxHeap(n); + for( int i = 0 ; i < n ; i ++ ) + maxHeap.insert(arr[i]); + + for( int i = n-1 ; i >= 0 ; i -- ) + arr[i] = maxHeap.extractMax(); + } + + + // 只通过shiftDown操作进行排序 + public static void sort2(Comparable[] arr){ + int n = arr.length; + + // 注意,此时我们的堆是从0开始索引的 + // 从(最后一个元素的索引-1)/2开始 + // 最后一个元素的索引 = n-1 + for( int i = (n-1-1)/2 ; i >= 0 ; i -- ) + shiftDown2(arr, n, i); + + for( int i = n-1; i > 0 ; i-- ){ // 这个的目的是让序列从小到大排序 + swap( arr, 0, i); + shiftDown2(arr, i, 0); + } + } + + // 交换堆中索引为i和j的两个元素 + private static void swap(Object[] arr, int i, int j){ + Object t = arr[i]; + arr[i] = arr[j]; + arr[j] = t; + } + + // 原始的shiftDown过程 + private static void shiftDown(Comparable[] arr, int n, int k){ + while( 2*k+1 < n ){ + int j = 2*k+1; + if( j+1 < n && arr[j+1].compareTo(arr[j]) > 0 ) + j += 1; + + if( arr[k].compareTo(arr[j]) >= 0 )break; + + swap( arr, k, j); + k = j; + } + } + + // 优化的shiftDown过程, 使用赋值的方式取代不断的swap, + // 该优化思想和我们之前对插入排序进行优化的思路是一致的 + private static void shiftDown2(Comparable[] arr, int n, int k){ + + Comparable e = arr[k]; + while( 2*k+1 < n ){ + int j = 2*k+1; + if( j+1 < n && arr[j+1].compareTo(arr[j]) > 0 ) + j += 1; + + if( e.compareTo(arr[j]) >= 0 ) + break; + + arr[k] = arr[j]; + k = j; + } + + arr[k] = e; + } + + // 测试 HeapSort + public static void main(String[] args) { + Integer[] arr = {10, 91, 8, 7, 6, 5, 4, 3, 2, 1}; + HeapSort.sort2(arr); + PrintHelper.printArray(arr); + } +} +``` + +#### 6. 堆排序的应用——Top K问题 + +例如,有1亿个浮点数,如何找出其中最大的10000个?(B326) + + + +### 8. 计数排序 + +https://www.cnblogs.com/freedom314/p/5847092.html + + + +### 9. 排序算法总结 + +| | 平均时间复杂度 | 原地排序 | 额外空间 | 稳定排序 | +| :------: | :------------: | :------: | :------: | :------: | +| 插入排序 | O(n^2) | √ | O(1) | √ | +| 归并排序 | O(nlogn) | × | O(n) | √ | +| 快速排序 | O(nlogn) | √ | O(logn) | × | +| 堆排序 | O(nlogn) | √ | O(1) | × | + +稳定排序:对于相等的元素,在排序后,原来靠前的元素依然靠前。相等元素的相对位置没有发生变化。 + +```java +// 可以通过⾃自定义⽐比较函数,让排序算法不不存在稳定性的问题。 +bool operator<(const Student& otherStudent){ + return score != otherStudent.score ? + score > otherStudent.score : + name < otherStudent.name; +} +``` + + + + + + + +![](../pics/sort_algorithm_analyze.png) + + + +## 二、二分查找法 + +Leetcode 704. + + + + + +## 三、递归和回溯法 + +- Leetcode 70. + +- Leetcode 17. + + + +## 四、分治的思想 + + + + + +## 五、动态规划 + +- 9-1 什么是动态规划 (20:27) + +- 9-2 第一个动态规划问题 Climbing Stairs (14:02) + + - [70. 爬楼梯](https://leetcode-cn.com/problems/climbing-stairs/) + + - 【作业】[120. 三角形最小路径和](https://leetcode-cn.com/problems/triangle/) + + - 【作业】[64. 最小路径和](https://leetcode-cn.com/problems/minimum-path-sum/) + +- 9-3 发现重叠子问题 Integer Break (25:10) + + - [343. 整数拆分](https://leetcode-cn.com/problems/integer-break/) + + - 【作业】279 + + - 【作业】91 + + - 【作业】62 + + - 【作业】63 + +- 9-4 状态的定义和状态转移 House Robber (27:19) + + - [198. 打家劫舍](https://leetcode-cn.com/problems/house-robber/) + + - 【作业】213 + + - 【作业】337 + + - 【作业】309 + +- 9-5 0-1背包问题 (32:37) + +- 9-6 0-1背包问题的优化和变种 (20:18) + +- 9-7 面试中的0-1背包问题 Partition Equal Subset Sum (27:34) + +- 9-8 LIS问题 Longest Increasing Subsequence (25:12) + + - [300. 最长递增子序列](https://leetcode-cn.com/problems/longest-increasing-subsequence/) + + - 【作业】[376. 摆动序列](https://leetcode-cn.com/problems/wiggle-subsequence/) + +- 9-9 LCS,最短路,求动态规划的具体解以及更多 (21:00) + - [1143. 最长公共子序列](https://leetcode-cn.com/problems/longest-common-subsequence/) + +- 9-10 动态规划的经典问题 + +- https://www.cxyzjd.com/article/qq_42874319/115740445 + +- + +## 六、贪心算法 08/10 + +- 10-1 贪心基础 Assign Cookies (12:12) + + - [455. 分发饼干](https://leetcode-cn.com/problems/assign-cookies/) + + - 【作业】[392. 判断子序列](https://leetcode-cn.com/problems/is-subsequence/) + +- 10-2 贪心算法与动态规划的关系 Non-overlapping Intervals (17:58) + - [435. 无重叠区间](https://leetcode-cn.com/problems/non-overlapping-intervals/) + +- 10-3 贪心选择性质的证明 (15:19) + + + +## 七、模式匹配 + +- KMP + +- RK + + + +## 八、搜索算法(深搜,广搜等) + + + +## 九、并查集 diff --git "a/notes/\346\212\200\346\234\257\344\272\244\346\265\201\347\276\244.md" "b/notes/\346\212\200\346\234\257\344\272\244\346\265\201\347\276\244.md" index eeb10141..441f6716 100644 --- "a/notes/\346\212\200\346\234\257\344\272\244\346\265\201\347\276\244.md" +++ "b/notes/\346\212\200\346\234\257\344\272\244\346\265\201\347\276\244.md" @@ -10,7 +10,7 @@ 这里没有广告,只有纯粹的技术问题分享,不定期的干货和学习资源分享 -
+
diff --git "a/notes/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225.md" "b/notes/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225.md" index 8353d27d..74a71393 100644 --- "a/notes/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225.md" +++ "b/notes/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225.md" @@ -1,73 +1,6 @@ - - -- [前言](#前言) -- [第一部分:数据结构](#第一部分数据结构) - - [一、线性表](#一线性表) - - [二、栈和队列](#二栈和队列) - - [三、树和二叉树](#三树和二叉树) - - [1. 红黑树](#1-红黑树) - - [2. 二叉树](#2-二叉树) - - [二分查找法](#二分查找法) - - [二叉树遍历](#二叉树遍历) - - [3. 二分搜索树](#3-二分搜索树) - - [深度优先遍历(前序、中序、后序遍历)](#深度优先遍历前序中序后序遍历) - - [广度优先遍历(层序遍历)](#广度优先遍历层序遍历) - - [4. AVL树](#4-avl树) - - [5. B和B+](#5-b和b) - - [四、字符串和数组](#四字符串和数组) -- [第二部分:算法思想](#第二部分算法思想) - - [一、排序](#一排序) - - [1. 选择排序(Selection Sort)](#1-选择排序selection-sort) - - [2. 插入排序(Insertion Sort)](#2-插入排序insertion-sort) - - [3. 冒泡排序(Bubble Sort)](#3-冒泡排序bubble-sort) - - [4. 希尔排序(Shell Sort)](#4-希尔排序shell-sort) - - [5. 归并排序(Merge Sort)](#5-归并排序merge-sort) - - [6. 快速排序(Quick Sort)](#6-快速排序quick-sort) - - [1. 普通快速排序](#1-普通快速排序) - - [2. 双路快速排序](#2-双路快速排序) - - [3. 三路快速排序](#3-三路快速排序) - - [7. 堆排序(Heap Sort)](#7-堆排序heap-sort) - - [1. 堆](#1-堆) - - [2. 上浮和下沉](#2-上浮和下沉) - - [3.插入元素](#3插入元素) - - [4. 删除最大元素](#4-删除最大元素) - - [5. 堆排序](#5-堆排序) - - [6. 堆排序的应用——Top K问题](#6-堆排序的应用top-k问题) - - [8. 计数排序和流排序](#8-计数排序和流排序) - - [9. 排序算法总结](#9-排序算法总结) - - [二、递归和回溯法](#二递归和回溯法) - - [1. 例题](#1-例题) - - [2. 排列问题](#2-排列问题) - - [3. 组合问题](#3-组合问题) - - [4. 回溯法的剪枝](#4-回溯法的剪枝) - - [5. 二维平面回溯法](#5-二维平面回溯法) - - [6. floodfill算法](#6-floodfill算法) - - [三、动态规划](#三动态规划) - - [1. 斐波那契数列](#1-斐波那契数列) - - [1.1 递归方式(自顶向下)](#11-递归方式自顶向下) - - [1.2 记忆化搜索(自底向上)](#12-记忆化搜索自底向上) - - [1.3 动态规划](#13-动态规划) - - [2. 背包问题](#2-背包问题) - - [(1)记忆化搜索](#1记忆化搜索) - - [(2)动态规划](#2动态规划) - - [(3)动态规划优化思路1](#3动态规划优化思路1) - - [(4)动态规划优化思路2](#4动态规划优化思路2) - - [(5)背包问题更多变种](#5背包问题更多变种) - - [3. 最长上升子序列](#3-最长上升子序列) - - [4. 最长公共子序列](#4-最长公共子序列) - - [四、贪心算法](#四贪心算法) - - [1. assign-cookies](#1-assign-cookies) -- [第三部分:面试指南](#第三部分面试指南) - - [1. 判单链表是否对称](#1-判单链表是否对称) - - [2. 合并两个有序数组成一个有序数组](#2-合并两个有序数组成一个有序数组) - - [3. 求二叉树中值为x的结点的层号](#3-求二叉树中值为x的结点的层号) - - [阿里面经OneNote](#阿里面经onenote) -- [第四部分:参考资料](#第四部分参考资料) - - # 前言 -本文将系统总结算法面试和经典数据结构相关知识点,在这里分成 【数据结构】 和 【算法】 两部分展开。这里将展示主要的核心知识点,关于代码面试的Leetcode习题请转向代码仓库:[Interview-code](https://github.com/frank-lam/interview_code) +本文将系统总结算法面试和经典数据结构相关知识点,在这里分成 【数据结构】 和 【算法】 两部分展开。这里将展示主要的核心知识点,关于代码面试的 Leetcode 习题请转向代码仓库:[Interview-code](https://github.com/frank-lam/interview_code) @@ -94,6 +27,8 @@ - 数组 - 链表 + + ## 二、栈和队列 @@ -168,9 +103,9 @@ private static int searchDfs(int[] data, int l, int r, int target) { } ``` -##### -### 4.. 二叉树遍历 + +### 4. 二叉树遍历 #### 深度优先遍历(前序、中序、后序遍历) @@ -300,6 +235,14 @@ public: +## 五、图结构 + +### 邻接矩阵 + +### 邻接表 + + + # 第二部分:算法思想 ## 一、排序 @@ -351,13 +294,15 @@ private static void swap(int[] arr, int i, int j) { ```java public static void sort(int[] arr) { - for (int i = 0; i < arr.length - 1; i++) { - for (int j = i + 1; j > 0; j--) { - if (arr[j] < arr[j - 1]) - swap(arr, j, j - 1); // 大量的交换会消耗时间 - else - break; + for (int i = 0; i < arr.length; i++) { + // 选择 arr[i...n) 中的最小值 + int minIndex = i; + for (int j = i + 1; j < arr.length; j++) { + if (arr[minIndex] > arr[j]) { + minIndex = j; + } } + swap(arr, i, minIndex); } } @@ -385,7 +330,7 @@ private static void swap(int[] arr, int i, int j) { **算法分析** -插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。 +插入排序在实现上,通常采用 in-place 排序(即只需用到 O(1) 的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。 @@ -1676,7 +1621,6 @@ void level_in_x(BinTree BT,char x,int level) # 第四部分:参考资料 - [数据结构与算法系列 目录 - 如果天空不死 - 博客园](http://www.cnblogs.com/skywang12345/p/3603935.html) -- [Interview-Notebook/算法.md at master · CyC2018/Interview-Notebook](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E7%AE%97%E6%B3%95.md#%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F) - [十大经典排序算法](https://www.cnblogs.com/onepixel/articles/7674659.html) - [VisuAlgo - visualising data structures and algorithms through animation](https://visualgo.net/en) - [Data Structure Visualization](https://www.cs.usfca.edu/~galles/visualization/Algorithms.html) @@ -1684,4 +1628,3 @@ void level_in_x(BinTree BT,char x,int level) -