算法与数据结构
- javascript
- typescript
1、数据结构
- 常用数据结构
- 字符串
- 数组
- 链表
- 栈
- 队列
- 树
- 高级数据结构
- 图
- 前缀树
- 线段树
- 树状数组
- 主席树
2、实现常用的数据结构
2.1 如何调试代码
2.1.1 如何调试 js 代码
安装一个可视化插件:Debug Visualizer。
ctrl + shift + D 打开运行和调试界面,添加 nodejs 启动程序。
修改一下 launch.json 中的文件夹路径,匹配到你要调试的 js 文件。
按 F5 或者运行调试页面的的开始调试按钮。
按 F10 逐步调试。
按 ctrl + shift + p 打开 Debug Visualizer 界面。在界面中输入 stack 变量,可以看到该变量的可视化结果。
2.1.2 如何调试 ts 代码
全局安装下 typescript
npm install typescript -g
在项目下新建 tsconfig.json 文件:
{
"compilerOptions": {
"target": "es2015", // 编译出 JavaScript 代码的 ECMAScript 版本
"module": "commonjs", // 编译出 JavaScript 文件的模块格式
"outDir": "out", // 编译出 JavaScript 文件的存放位置;
"sourceMap": true // 是否生成 SourceMap 文件;(不生成 SourceMap 不会进端点)
},
"include": ["ts/**/*"]
}
在 .vscode/ 中新建 tasks.json
{
"version": "2.0.0",
"tasks": [
{
"type": "typescript",
"tsconfig": "tsconfig.json",
"problemMatcher": ["$tsc"],
"group": "build",
"label": "tsc: build - tsconfig.json"
}
]
}
修改 .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Program Js",
"program": "${workspaceFolder}/js/index.js",
"request": "launch",
"skipFiles": ["<node_internals>/**"],
"type": "node"
},
{
"name": "Launch Program Ts",
"program": "${workspaceFolder}/ts/index.ts",
"preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": ["${workspaceFolder}/out/**/*.js"],
"request": "launch",
"skipFiles": ["<node_internals>/**"],
"type": "node"
}
]
}
这样就可以调试了
3、实现栈
- 受限的线性表,只允许在表的一端进行插入删除操作,这一端被称为栈顶,另一端称为栈底
- 后进先出
- 封装
- push 增加新元素到栈顶
- pop 移除栈顶的元素
- peek 返回栈顶的元素,不对栈做任何修改
- isEmpty 返回是否有元素值的布尔类型
- size 返回栈里的元素个数
- clear 清空栈中的元素
- toString 将栈结构的内容以字符形式返回
3.1 js 版本
/**
* * 实现一个栈结构
* @constructor
*/
class Stack {
constructor() {
this.STACK_EMPTY_DESC = "栈是空的";
this.items = [];
}
/**
* 向栈中添加元素
* @param {*} item
*/
push(item) {
this.items.push(item);
}
/**
* 从栈中移除元素
* @returns {Array<any> | string}
*/
pop() {
if (this.isEmpty()) {
return this.STACK_EMPTY_DESC;
}
return this.items.pop();
}
/**
* 查看栈顶元素
* @returns
*/
peek() {
if (this.isEmpty()) {
return this.STACK_EMPTY_DESC;
}
return this.items[this.items.length - 1];
}
/**
* 检查栈是否空
* @returns
*/
isEmpty() {
return this.items.length === 0 ? true : false;
}
/**
* 获取栈中元素数量
* @returns
*/
size() {
return this.items.length;
}
/**
* 清空栈中所有元素
*/
clear() {
this.items = [];
}
}
// 测试
const stack = new Stack();
stack.push(1);
stack.push(2);
console.log(stack.peek()); // 输出: 2
console.log(stack.pop()); // 输出: 2
console.log(stack.size()); // 输出: 1
console.log(stack.isEmpty()); // 输出: false
stack.clear();
console.log(stack.isEmpty()); // 输出: true
3.2 ts 版本
ts 可以使用泛型
/**
* * 实现一个栈结构
* @constructor
*/
class Stack<T> {
private items: T[] = [];
private STACK_EMPTY_DESC = "栈是空的";
/**
* 向栈中添加元素
* @param {T} item
*/
push(item: T): void {
this.items.push(item);
}
/**
* 从栈中移除元素
* @returns
*/
pop(): T | undefined | string {
if (this.isEmpty()) {
return this.STACK_EMPTY_DESC;
}
return this.items.pop();
}
/**
* 查看栈顶元素
* @returns
*/
peek(): T | string {
if (this.isEmpty()) {
return this.STACK_EMPTY_DESC;
}
return this.items[this.items.length - 1];
}
/**
* 检查栈是否空
* @returns
*/
isEmpty(): boolean {
return this.items.length === 0;
}
/**
* 获取栈中元素数量
* @returns
*/
size(): number {
return this.items.length;
}
/**
* 清空栈中所有元素
*/
clear(): void {
this.items = [];
}
}
// 测试
const stack = new Stack();
stack.push(1);
stack.push(2);
console.log(stack.peek()); // 输出: 2
console.log(stack.pop()); // 输出: 2
console.log(stack.size()); // 输出: 1
console.log(stack.isEmpty()); // 输出: false
stack.clear();
console.log(stack.isEmpty()); // 输出: true
4、实现单向链表
实现单向链表
- 数组的不足
- 插入数据时时间复杂度是 O(n)
- 相较数组链表的优势
- 插入和删除数据时,时间复杂度可达到 O(1),相对于数组效率会高很多。
- 链表的缺陷
- 无法通过下标直接访问元素,需要从头一个个访问,直到找到对应的元素。
链表的结构:
- 在链表中,每一个元素都包含两个属性:元素的值 item 和 下一个元素 next,next 指向下一个节点。
- 链表中有一个指针 head 指向链表第一个元素,最后一个元素则指向 null。
链表的常见操作:
- 向链表尾部添加一个新的项。append(element)
- 向链表的特定位置插入一个新的项。insert(position,element)
- 获取对应位置的元素。get(position)
- 返回元素在链表中的索引。如果链表中没有该元素则返回-1。indexOf(element)
- 修改某个位置的元素。update(position ,element)
- 从列表的特定位置移除一项。removeAt(position)
- 从链表中移除一项。remove(element)
- 如果链表中不包含任何元素,返回 true,如果链表的长度大于 0 则返回 false。isEmpty()
- 返回链表包含的元素个数。与数组 length 属性类似。size()
4.1 js 版本
/**
* * 单向链表-节点
* @constructor
*/
class ListNode {
constructor(element) {
// 保存元素
this.element = element;
// 指向下一个节点
this.next = null;
}
}
/**
* * 单向链表
* @constructor
*/
class LinkedList {
constructor() {
// 头结点指针
this.head = null;
// 统计节点数量
this.length = 0;
}
/**
* 向链表尾部添加一个新的项
* @param {*} element 链表节点
*/
append(element) {
// 根据 element 创建 Node 对象
const newNode = new ListNode(element);
// 追加到最后 判断链表结构 head 是否为空(为空就表示链表中没有任何节点),将新创建的节点赋值给 head,此时表示该链表中已有节点
if (!this.head) {
this.head = newNode;
}
// 如果 head 不为空(表示链表中已有节点),最开始定义一个变量将头节点(head)赋值给 current(也就是指向第一个节点)
// 利用 while 循环依次判断是否存在下一个节点,如果不存在,就将这个新创建的节点赋值给最后一个节点的 next 属性
else {
let current = this.head;
while (current.next) {
current = current.next;
}
current.next = newNode;
}
this.length++;
}
/**
* 向链表的特定位置插入一个新的项
* @param {*} position 节点位置
* @param {*} element 链表节点
*/
insert(position, element) {
// 判断输入的值是否越界
if (position < 0 || position > this.length) {
return false;
}
// 创建新的节点
const newNode = new ListNode(element);
// 插入元素
// 判断插入元素的位置是否在头节点(head)
if (position === 0) {
// 插入的节点在第一个就将此时指向头节点的节点赋值给插入节点的next属性
newNode.next = this.head;
//之后再将新插入的节点赋值给头节点
this.head = newNode;
}
// 在其他位置插入节点 ,需要将插入位置的新节点前一个节点指向该新节点,再将新节点的next属性指向后一个节点
else {
let index = 0;
let current = this.head;
// 插入节点的前一个位置
let previous = null;
while (index++ < position) {
previous = current;
current = current.next;
}
previous.next = newNode;
newNode.next = current;
}
this.length++;
// 表示插入成功
return true;
}
/**
* 获取对应位置的元素
* @param {*} position 节点位置
*/
get(position) {
if (position < 0 || position > this.length - 1) return null;
// 查找指定位置的元素时,需要一个一个的往后找,直到找到指定位置将此位置的节点 next 属性赋值给变量 current
let index = 0;
let current = this.head;
while (index++ < position) {
current = current.next;
}
return current.element;
}
/**
* 返回元素在链表中的索引。如果链表中没有该元素则返回-1。indexOf(element)
* @param {*} element 链表节点
*/
indexOf(element) {
let current = this.head;
let index = 0;
while (current) {
if (current.element === element) {
return index;
}
index++;
current = current.next;
}
return -1;
}
/**
* 修改某个位置的元素
* @param {*} position 节点位置
* @param {*} element 链表节点
* @returns
*/
update(position, element) {
let result = this.removeAt(position);
this.insert(position, element);
return result;
}
/**
* 从列表的特定位置移除一项
* @param {*} position 节点位置
* @returns
*/
removeAt(position) {
if (position < 0 || position > this.length - 1) return null;
let current = this.head;
let previous = null;
let index = 0;
if (position === 0) {
this.head = current.next;
} else {
while (index++ < position) {
previous = current;
current = current.next;
}
previous.next = current.next;
}
this.length--;
return current.element;
}
/**
* 从链表中移除一项
* @param {*} element
* @returns
*/
remove(element) {
const index = this.indexOf(element);
if (index === -1) return;
this.removeAt(index);
}
/**
* 如果链表中不包含任何元素,返回 true,如果链表的长度大于 0 则返回 false
* @returns
*/
isEmpty() {
return this.length === 0;
}
/**
* 返回链表包含的元素个数。与数组length属性类似
* @returns
*/
size() {
return this.length;
}
}
const linkedList = new LinkedList();
// 测试向链表添加节点
linkedList.append("a");
linkedList.append("b");
linkedList.append("c");
linkedList.append("d");
linkedList.append("e");
console.log(linkedList);
// 测试向指定位置插入节点
const res = linkedList.insert("1", "f");
console.log(linkedList, res);
// 测试获取指定位置的元素
console.log(linkedList.get(1));
// 测试通过元素获取下标值{}
console.log(linkedList.indexOf("b"));
// 测试从列表的特定位置移除一项
console.log(linkedList.removeAt(4));
console.log(linkedList);
// 测试修改某个位置的元素
console.log(linkedList.update(1, "www"));
console.log(linkedList);
// 测试从链表中移除一项
console.log(linkedList.remove("a"));
console.log(linkedList);
4.2 ts 版本
/**
* * 单向链表-节点
* @constructor
*/
class ListNode<T> {
element: T;
next: ListNode<T> | null;
constructor(element: T) {
// 保存元素
this.element = element;
// 指向下一个节点
this.next = null;
}
}
/**
* * 单向链表
* @constructor
*/
class LinkedList<T> {
private head: ListNode<T> | null;
private length: number;
constructor() {
// 头结点指针
this.head = null;
// 统计节点数量
this.length = 0;
}
/**
* 向链表尾部添加一个新的项
* @param {*} element 链表节点
*/
append(element: T) {
// 根据 element 创建 Node 对象
const newNode = new ListNode(element);
// 追加到最后 判断链表结构 head 是否为空(为空就表示链表中没有任何节点),将新创建的节点赋值给 head,此时表示该链表中已有节点
if (!this.head) {
this.head = newNode;
}
// 如果 head 不为空(表示链表中已有节点),最开始定义一个变量将头节点(head)赋值给 current(也就是指向第一个节点)
// 利用 while 循环依次判断是否存在下一个节点,如果不存在,就将这个新创建的节点赋值给最后一个节点的 next 属性
else {
let current = this.head;
while (current.next) {
current = current.next;
}
current.next = newNode;
}
this.length++;
}
/**
* 向链表的特定位置插入一个新的项
* @param {*} position 节点位置
* @param {*} element 链表节点
*/
insert(position: number, element: T) {
// 判断输入的值是否越界
if (position < 0 || position > this.length) {
return false;
}
// 创建新的节点
const newNode = new ListNode(element);
// 插入元素
// 判断插入元素的位置是否在头节点(head)
if (position === 0) {
// 插入的节点在第一个就将此时指向头节点的节点赋值给插入节点的next属性
newNode.next = this.head;
//之后再将新插入的节点赋值给头节点
this.head = newNode;
}
// 在其他位置插入节点 ,需要将插入位置的新节点前一个节点指向该新节点,再将新节点的next属性指向后一个节点
else {
let index = 0;
let current = this.head;
// 插入节点的前一个位置
let previous = null;
while (index++ < position) {
previous = current;
current = current.next;
}
previous.next = newNode;
newNode.next = current;
}
this.length++;
// 表示插入成功
return true;
}
/**
* 获取对应位置的元素
* @param {*} position 节点位置
*/
get(position: number) {
if (position < 0 || position > this.length - 1) return null;
// 查找指定位置的元素时,需要一个一个的往后找,直到找到指定位置将此位置的节点 next 属性赋值给变量 current
let index = 0;
let current = this.head;
while (index++ < position) {
current = current.next;
}
return current.element;
}
/**
* 返回元素在链表中的索引。如果链表中没有该元素则返回-1。indexOf(element)
* @param {*} element 链表节点
*/
indexOf(element: T) {
let current = this.head;
let index = 0;
while (current) {
if (current.element === element) {
return index;
}
index++;
current = current.next;
}
return -1;
}
/**
* 修改某个位置的元素
* @param {*} position 节点位置
* @param {*} element 链表节点
* @returns
*/
update(position: number, element: T) {
let result = this.removeAt(position);
this.insert(position, element);
return result;
}
/**
* 从列表的特定位置移除一项
* @param {*} position 节点位置
* @returns
*/
removeAt(position: number) {
if (position < 0 || position > this.length - 1) return null;
let current = this.head;
let previous = null;
let index = 0;
if (position === 0) {
this.head = current.next;
} else {
while (index++ < position) {
previous = current;
current = current.next;
}
previous.next = current.next;
}
this.length--;
return current.element;
}
/**
* 从链表中移除一项
* @param {*} element
* @returns
*/
remove(element: T) {
const index = this.indexOf(element);
if (index === -1) return;
this.removeAt(index);
}
/**
* 如果链表中不包含任何元素,返回 true,如果链表的长度大于 0 则返回 false
* @returns
*/
isEmpty() {
return this.length === 0;
}
/**
* 返回链表包含的元素个数。与数组length属性类似
* @returns
*/
size() {
return this.length;
}
}
const linkedList = new LinkedList();
// 测试向链表添加节点
linkedList.append("a");
linkedList.append("b");
linkedList.append("c");
linkedList.append("d");
linkedList.append("e");
console.log(linkedList);
// 测试向指定位置插入节点
const res = linkedList.insert(1, "f");
console.log(linkedList, res);
// 测试获取指定位置的元素
console.log(linkedList.get(1));
// 测试通过元素获取下标值{}
console.log(linkedList.indexOf("b"));
// 测试从列表的特定位置移除一项
console.log(linkedList.removeAt(4));
console.log(linkedList);
// 测试修改某个位置的元素
console.log(linkedList.update(1, "www"));
console.log(linkedList);
// 测试从链表中移除一项
console.log(linkedList.remove("a"));
console.log(linkedList);
5、实现双向链表
- 单向链表的缺陷:返回前一个节点(需要从头再开始遍历)
实现的原理
- 基于单向链表实现
- 在每个节点中添加一个指向上一个节点的引用(prev),第一个节点的 prev 指向的是 null,最后一个节点的 next 指向的也是 null
缺陷
- 在插入或者删除某个节点时,需要处理四个引用
- 占用的内存空间更大
5.1 js 版本
/**
* * 双向链表-节点
*/
class DoublyNode extends ListNode {
constructor(element) {
super(element);
this.prev = null;
}
}
/**
* * 双向链表
*/
class DoublyLinkedList extends LinkedList {
constructor() {
super();
this.tail = null;
}
/**
* 向链表尾部添加一个新的项
* @param {*} element
*/
append(element) {
const newNode = new DoublyNode(element);
if (this.head === null) {
this.head = newNode;
this.tail = newNode;
} else {
this.tail.next = newNode;
newNode.prev = this.tail;
this.tail = newNode;
}
this.length++;
}
/**
* 向链表的特定位置插入一个新的项
* @param {*} position
* @param {*} element
* @returns
*/
insert(position, element) {
if (position < 0 || position > this.length) return false;
const newNode = new DoublyNode(element);
if (position === 0) {
if (this.head == null) {
this.head = newNode;
this.tail = newNode;
} else {
newNode.next = this.head;
this.head.prev = newNode;
this.head = newNode;
}
} else if (position === this.length) {
this.tail.next = newNode;
newNode.prev = this.tail;
this.tail = newNode;
} else {
let index = 0;
let current = this.head;
let previous = null;
while (index++ < position) {
previous = current;
current = current.next;
}
previous.next = newNode;
newNode.prev = previous;
newNode.next = current;
current.prev = newNode;
}
}
/**
* 获取对应位置的元素
* @param {*} position
* @returns
*/
removeAt(position) {
if (position < 0 || position > this.length - 1) return null;
let current = this.head;
if (position === 0) {
if (this.length === 1) {
this.head = null;
this.tail = null;
} else {
this.head = this.head.next;
this.head.prev = null;
}
} else if (position === this.length - 1) {
current = this.tail;
this.tail.prev.next = null;
this.tail = this.tail.prev;
} else {
let index = 0;
let previous = null;
while (index++ < position) {
previous = current;
current = current.next;
}
previous.next = current.next;
current.next.prev = previous;
}
return current.element;
}
}
const doublyLinkedList = new DoublyLinkedList();
doublyLinkedList.append("a");
doublyLinkedList.append("b");
doublyLinkedList.append("c");
doublyLinkedList.append("d");
doublyLinkedList.append("e");
doublyLinkedList.append("f");
console.log(doublyLinkedList);
doublyLinkedList.insert(1, "g");
console.log(doublyLinkedList);
console.log(doublyLinkedList.get(1));
console.log(doublyLinkedList.indexOf("g"));
console.log(doublyLinkedList.removeAt(1));
console.log(doublyLinkedList.update(0, "nnn"));
console.log(doublyLinkedList.remove("f"));
5.2 ts 版本
/**
* * 双向链表-节点
*/
class DoublyNode<T> extends ListNode<any> {
prev: DoublyNode<T> | null;
constructor(element: T) {
super(element);
this.prev = null;
}
}
/**
* * 双向链表
*/
class DoublyLinkedList<T> extends LinkedList<any> {
tail: DoublyNode<T> | null;
constructor() {
super();
this.tail = null;
}
/**
* 向链表尾部添加一个新的项
* @param {*} element
*/
append(element: T) {
const newNode = new DoublyNode(element);
if (this.head === null) {
this.head = newNode;
this.tail = newNode;
} else {
this.tail.next = newNode;
newNode.prev = this.tail;
this.tail = newNode;
}
this.length++;
}
/**
* 向链表的特定位置插入一个新的项
* @param {*} position
* @param {*} element
* @returns
*/
insert(position: number, element: T) {
if (position < 0 || position > this.length) return false;
const newNode = new DoublyNode(element);
if (position === 0) {
if (this.head == null) {
this.head = newNode;
this.tail = newNode;
} else {
newNode.next = this.head;
this.head.prev = newNode;
this.head = newNode;
}
} else if (position === this.length) {
this.tail.next = newNode;
newNode.prev = this.tail;
this.tail = newNode;
} else {
let index = 0;
let current = this.head;
let previous = null;
while (index++ < position) {
previous = current;
current = current.next;
}
previous.next = newNode;
newNode.prev = previous;
newNode.next = current;
current.prev = newNode;
}
}
/**
* 获取对应位置的元素
* @param {*} position
* @returns
*/
removeAt(position: number) {
if (position < 0 || position > this.length - 1) return null;
let current = this.head;
if (position === 0) {
if (this.length === 1) {
this.head = null;
this.tail = null;
} else {
this.head = this.head.next;
this.head.prev = null;
}
} else if (position === this.length - 1) {
current = this.tail;
this.tail.prev.next = null;
this.tail = this.tail.prev;
} else {
let index = 0;
let previous = null;
while (index++ < position) {
previous = current;
current = current.next;
}
previous.next = current.next;
current.next.prev = previous;
}
return current.element;
}
}
const doublyLinkedList = new DoublyLinkedList();
doublyLinkedList.append("a");
doublyLinkedList.append("b");
doublyLinkedList.append("c");
doublyLinkedList.append("d");
doublyLinkedList.append("e");
doublyLinkedList.append("f");
console.log(doublyLinkedList);
doublyLinkedList.insert(1, "g");
console.log(doublyLinkedList);
console.log(doublyLinkedList.get(1));
console.log(doublyLinkedList.indexOf("g"));
console.log(doublyLinkedList.removeAt(1));
console.log(doublyLinkedList.update(0, "nnn"));
console.log(doublyLinkedList.remove("f"));
6、实现队列
6.1 js 版本
/**
* * 使用数组实现队列
*/
class Queue {
constructor() {
this.STACK_EMPTY_DESC = "队列是空的";
this.items = [];
}
/**
* 入队操作
* @param {*} element
*/
enqueue(element) {
this.items.push(element);
}
/**
* 出队操作
*/
dequeue() {
if (this.isEmpty()) {
return this.STACK_EMPTY_DESC;
}
return this.items.shift();
}
/**
* 查看队首元素
* @returns
*/
front() {
if (this.isEmpty()) {
return "队列为空";
}
return this.items[0];
}
/**
* 检查队列是否为空
*/
isEmpty() {
return this.items.length === 0;
}
/**
* 获取队列的大小
*/
size() {
return this.items.length;
}
/**
* 打印队列内容(可选)
*/
print() {
console.log(this.items.toString());
}
/**
* 清空队列
*/
clear() {
this.items = [];
}
}
console.log("==queue==");
const queue = new Queue();
console.log(queue.isEmpty()); // 应该输出 true,因为队列为空
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
queue.print(); // 应该输出 1,2,3,表示元素按顺序入队成功
console.log(queue.front()); // 应该输出 1,查看队首元素
console.log(queue.dequeue()); // 应该输出 1,并从队列中移除它
console.log(queue.size()); // 应该输出 2,因为现在队列中有两个元素了
console.log(queue.isEmpty()); // 应该输出 false,因为队列不为空了
queue.print(); // 应该输出 2,3,表示元素按顺序出队成功后的队列状态
6.2 ts 版本
/**
* * 使用数组实现队列
*/
class Queue<T> {
STACK_EMPTY_DESC: string;
items: Array<T>;
constructor() {
this.STACK_EMPTY_DESC = "队列是空的";
this.items = [];
}
/**
* 入队操作
* @param {*} element
*/
enqueue(element: T) {
this.items.push(element);
}
/**
* 出队操作
*/
dequeue() {
if (this.isEmpty()) {
return this.STACK_EMPTY_DESC;
}
return this.items.shift();
}
/**
* 查看队首元素
* @returns
*/
front() {
if (this.isEmpty()) {
return "队列为空";
}
return this.items[0];
}
/**
* 检查队列是否为空
*/
isEmpty() {
return this.items.length === 0;
}
/**
* 获取队列的大小
*/
size() {
return this.items.length;
}
/**
* 打印队列内容(可选)
*/
print() {
console.log(this.items.toString());
}
/**
* 清空队列
*/
clear() {
this.items = [];
}
}
console.log("==queue==");
const queue = new Queue();
console.log(queue.isEmpty()); // 应该输出 true,因为队列为空
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
queue.print(); // 应该输出 1,2,3,表示元素按顺序入队成功
console.log(queue.front()); // 应该输出 1,查看队首元素
console.log(queue.dequeue()); // 应该输出 1,并从队列中移除它
console.log(queue.size()); // 应该输出 2,因为现在队列中有两个元素了
console.log(queue.isEmpty()); // 应该输出 false,因为队列不为空了
queue.print(); // 应该输出 2,3,表示元素按顺序出队成功后的队列状态
7、二叉树
7.1 js 版本
/**
* * 实现二叉树
*/
/** 节点类:保存节点信息和指针 */
class TreeNode {
left;
right;
key;
constructor(key) {
this.key = key;
this.left = null;
this.right = null;
}
}
/** 二叉搜索树 */
class BinarySearchTree {
root;
constructor(key) {
this.root = null;
}
/**
* 插入节点
* @param {*} key
*/
insert(key) {
const newNode = new TreeNode(key);
// 如果根节点为空
if (this.root === null) {
this.root = newNode;
} else {
this.insertNode(this.root, newNode);
}
}
/**
* 插入节点的方法
* @param {*} root
* @param {*} newNode
*/
insertNode(root, newNode) {
// 如果已经有了,则禁止插入
if (root.key === newNode.key) {
console.log("======>该节点已经插入过了");
} else if (root.key > newNode.key) {
// 左侧查找
if (root.left === null) {
root.left = newNode;
} else {
this.insertNode(root.left, newNode);
}
} else {
// 右侧查找
if (root.right === null) {
root.right = newNode;
} else {
this.insertNode(root.right, newNode);
}
}
}
/**
* 先序遍历
* @param {*} root
* @param {*} handler
*/
preOrderTraversal(handler) {
this.preOrderTranversalNode(this.root, handler);
}
preOrderTranversalNode(root, handler) {
// 边界判断
if (!root) return;
// 访问根节点
handler(root.key);
// 访问左子树
this.preOrderTranversalNode(root.left, handler);
// 访问右子树
this.preOrderTranversalNode(root.right, handler);
}
/**
* 中序遍历
* @param {*} handler
*/
inOrderTraversal(handler) {
this.inOrderTraversalNode(this.root, handler);
}
inOrderTraversalNode(root, handler) {
if (!root) return;
this.inOrderTraversalNode(root.left, handler);
handler(root.key);
this.inOrderTraversalNode(root.right, handler);
}
/**
* 后序遍历
* @param {*} handler
*/
postOrderTraversal(handler) {
this.postOrderTraversalNode(this.root, handler);
}
postOrderTraversalNode(root, handler) {
if (!root) return;
this.postOrderTraversalNode(root.left, handler);
this.postOrderTraversalNode(root.right, handler);
handler(root.key);
}
min() {
let node = this.root;
while (node.left !== null) {
node = node.left;
}
return node.key;
}
max() {
let node = this.root;
while (node.right !== null) {
node = node.right;
}
return node.key;
}
// 递归的方式
search(key) {
return this.searchNode(this.root, key);
}
searchNode(root, key) {
if (!root) return false;
if (root.key < key) {
return this.searchNode(root.right, key);
} else if (root.key > key) {
return this.searchNode(root.left, key);
} else {
return true;
}
}
// 非递归的方式
searchV2(key) {
return this.searchNodeV2(this.root, key);
}
searchNodeV2(root, key) {
if (!root) return false;
while (root !== null) {
if (root.key < key) {
root = root.right;
} else if (root.key > key) {
root = root.left;
} else {
return true;
}
}
return false;
}
remove(key) {
// 当前遍历到的节点
let current = this.root;
// 当前节点的父级节点,因为需要更改父级指针
let parent = this.root;
// 记录是父级节点的左子节点还是右子节点
let isLeftChild = false;
// 查找需要删除的节点
while (current.key !== key) {
parent = current;
// 左子树查找
if (key < current.key) {
isLeftChild = true;
current = current.left;
} else {
// 右子树查找
isLeftChild = false;
current = current.right;
}
if (current === null) return false;
}
// 1.第一种情况,删除的是叶节点,考虑是否为根元素和非根元素两种情况
// 如果是根元素直接删除,如果不是根则根据isLeftChild,将parent的left||right置为null
if (current.left === null && current.right === null) {
// 只有一个根元素
if (current === this.root) {
this.root = null;
} else if (isLeftChild) {
parent.left = null;
} else {
parent.right = null;
}
}
// 2.第二种情况,当前遍历到的节点有一个子节点,考虑current是否为根和非根两种情况
// 左子树不为null,current只有左子树,这里赋值的都是current.left;
else if (current.right === null) {
if (current === this.root) {
// 这里是因为右子树为null
this.root = current.left;
} else if (isLeftChild) {
// 注意isLeftChild是用来决定使用的是parent的哪个指针
parent.left = current.left;
} else {
parent.right = current.left;
}
} else if (current.left === null) {
// 右子树不为null,current只有左子树,这里赋值的都是current.left;
if (current === this.root) {
this.root = current.right;
} else if (isLeftChild) {
parent.left = current.right;
} else {
parent.right = current.right;
}
}
// 3.第三种情况,左右子树都有,这时候需要考虑使用查找前驱、后继节点来替换删除的节点
// 前驱:比current小一点点的节点, 称为current节点的前驱.
// 后继:比current大一点点的节点, 称为current节点的后继.
else {
// 1.获取后继节点
let successor = this.getSuccessor(current);
// 2.判断是否是根节点
if (current == this.root) {
this.root = successor;
} else if (isLeftChild) {
parent.left = successor;
} else {
parent.right = successor;
}
// 3.将删除节点的左子树赋值给successor
successor.left = current.left;
}
return true;
}
getSuccessor(delNode) {
let parent = delNode;
let target = delNode;
// 从右子树开始
let current = delNode.right;
// 查找后继,左子树最小节点
while (current !== null) {
parent = target;
target = current;
current = current.left;
}
// 对于跨域层级的删除
if (target != delNode.right) {
parent.left = target.right;
target.right = delNode.right;
}
return target;
}
}
// 测试代码
const bst = new BinarySearchTree();
// 插入数据
bst.insert(11);
bst.insert(7);
bst.insert(15);
bst.insert(5);
bst.insert(3);
bst.insert(9);
bst.insert(8);
bst.insert(10);
bst.insert(13);
bst.insert(12);
bst.insert(14);
bst.insert(20);
bst.insert(18);
bst.insert(25);
bst.insert(6);
// 测试前序遍历结果
let resultString = "";
bst.preOrderTraversal(function (key) {
resultString += key + " ";
});
console.log("✅ ~ 先序遍历resultString:", resultString); // 11 7 5 3 6 9 8 10 15 13 12 14 20 18 25
resultString = "";
bst.inOrderTraversal(function (key) {
resultString += key + " ";
});
console.log("✅ ~ 中序遍历resultString:", resultString); // 3 5 6 7 8 9 10 11 12 13 14 15 18 20 25
resultString = "";
bst.postOrderTraversal(function (key) {
resultString += key + " ";
});
console.log("✅ ~ 后序遍历resultString:", resultString); // 3 6 5 8 10 9 7 12 14 13 18 25 20 15 11
// 获取最值
console.log("✅ ~ bst.min():", bst.min());
console.log("✅ ~ bst.max():", bst.max());
// 查找特定的值
console.log("✅ ~ bst.search(10):", bst.search(10));
console.log("✅ ~ bst.search(21):", bst.search(21));
// 查找特定的值
console.log("✅ ~ bst.searchV2(10):", bst.searchV2(10));
console.log("✅ ~ bst.searchV2(21):", bst.searchV2(21));
// 删除测试
console.log("✅ ~ bst.searchV2(10):", bst.remove(7));
resultString = "";
bst.postOrderTraversal(function (key) {
resultString += key + " ";
});
console.log("✅ ~ 后序遍历resultString:", resultString); // 3 6 5 8 10 9 7 12 14 13 18 25 20 15 11
7.2 ts 版本
/**
* * 实现二叉树
*/
/** 节点类:保存节点信息和指针 */
class TreeNode<T> {
left: null;
right: null;
key: T;
constructor(key: T) {
this.key = key;
this.left = null;
this.right = null;
}
}
/** 二叉搜索树 */
class BinarySearchTree<T> {
root: TreeNode<T>;
constructor() {
this.root = null;
}
/**
* 插入节点
* @param {*} key
*/
insert(key: T) {
const newNode = new TreeNode(key);
// 如果根节点为空
if (this.root === null) {
this.root = newNode;
} else {
this.insertNode(this.root, newNode);
}
}
/**
* 插入节点的方法
* @param {*} root
* @param {*} newNode
*/
insertNode(root, newNode) {
// 如果已经有了,则禁止插入
if (root.key === newNode.key) {
console.log("======>该节点已经插入过了");
} else if (root.key > newNode.key) {
// 左侧查找
if (root.left === null) {
root.left = newNode;
} else {
this.insertNode(root.left, newNode);
}
} else {
// 右侧查找
if (root.right === null) {
root.right = newNode;
} else {
this.insertNode(root.right, newNode);
}
}
}
/**
* 先序遍历
* @param {*} root
* @param {*} handler
*/
preOrderTraversal(handler) {
this.preOrderTranversalNode(this.root, handler);
}
preOrderTranversalNode(root, handler) {
// 边界判断
if (!root) return;
// 访问根节点
handler(root.key);
// 访问左子树
this.preOrderTranversalNode(root.left, handler);
// 访问右子树
this.preOrderTranversalNode(root.right, handler);
}
/**
* 中序遍历
* @param {*} handler
*/
inOrderTraversal(handler) {
this.inOrderTraversalNode(this.root, handler);
}
inOrderTraversalNode(root, handler) {
if (!root) return;
this.inOrderTraversalNode(root.left, handler);
handler(root.key);
this.inOrderTraversalNode(root.right, handler);
}
/**
* 后序遍历
* @param {*} handler
*/
postOrderTraversal(handler) {
this.postOrderTraversalNode(this.root, handler);
}
postOrderTraversalNode(root, handler) {
if (!root) return;
this.postOrderTraversalNode(root.left, handler);
this.postOrderTraversalNode(root.right, handler);
handler(root.key);
}
min() {
let node = this.root;
while (node.left !== null) {
node = node.left;
}
return node.key;
}
max() {
let node = this.root;
while (node.right !== null) {
node = node.right;
}
return node.key;
}
// 递归的方式
search(key) {
return this.searchNode(this.root, key);
}
searchNode(root, key) {
if (!root) return false;
if (root.key < key) {
return this.searchNode(root.right, key);
} else if (root.key > key) {
return this.searchNode(root.left, key);
} else {
return true;
}
}
// 非递归的方式
searchV2(key: T) {
return this.searchNodeV2(this.root, key);
}
searchNodeV2(root, key: T) {
if (!root) return false;
while (root !== null) {
if (root.key < key) {
root = root.right;
} else if (root.key > key) {
root = root.left;
} else {
return true;
}
}
return false;
}
remove(key: T) {
// 当前遍历到的节点
let current = this.root;
// 当前节点的父级节点,因为需要更改父级指针
let parent = this.root;
// 记录是父级节点的左子节点还是右子节点
let isLeftChild = false;
// 查找需要删除的节点
while (current.key !== key) {
parent = current;
// 左子树查找
if (key < current.key) {
isLeftChild = true;
current = current.left;
} else {
// 右子树查找
isLeftChild = false;
current = current.right;
}
if (current === null) return false;
}
// 1.第一种情况,删除的是叶节点,考虑是否为根元素和非根元素两种情况
// 如果是根元素直接删除,如果不是根则根据isLeftChild,将parent的left||right置为null
if (current.left === null && current.right === null) {
// 只有一个根元素
if (current === this.root) {
this.root = null;
} else if (isLeftChild) {
parent.left = null;
} else {
parent.right = null;
}
}
// 2.第二种情况,当前遍历到的节点有一个子节点,考虑current是否为根和非根两种情况
// 左子树不为null,current只有左子树,这里赋值的都是current.left;
else if (current.right === null) {
if (current === this.root) {
// 这里是因为右子树为null
this.root = current.left;
} else if (isLeftChild) {
// 注意isLeftChild是用来决定使用的是parent的哪个指针
parent.left = current.left;
} else {
parent.right = current.left;
}
} else if (current.left === null) {
// 右子树不为null,current只有左子树,这里赋值的都是current.left;
if (current === this.root) {
this.root = current.right;
} else if (isLeftChild) {
parent.left = current.right;
} else {
parent.right = current.right;
}
}
// 3.第三种情况,左右子树都有,这时候需要考虑使用查找前驱、后继节点来替换删除的节点
// 前驱:比current小一点点的节点, 称为current节点的前驱.
// 后继:比current大一点点的节点, 称为current节点的后继.
else {
// 1.获取后继节点
let successor = this.getSuccessor(current);
// 2.判断是否是根节点
if (current == this.root) {
this.root = successor;
} else if (isLeftChild) {
parent.left = successor;
} else {
parent.right = successor;
}
// 3.将删除节点的左子树赋值给successor
successor.left = current.left;
}
return true;
}
getSuccessor(delNode) {
let parent = delNode;
let target = delNode;
// 从右子树开始
let current = delNode.right;
// 查找后继,左子树最小节点
while (current !== null) {
parent = target;
target = current;
current = current.left;
}
// 对于跨域层级的删除
if (target != delNode.right) {
parent.left = target.right;
target.right = delNode.right;
}
return target;
}
}
// 测试代码
const bst = new BinarySearchTree();
// 插入数据
bst.insert(11);
bst.insert(7);
bst.insert(15);
bst.insert(5);
bst.insert(3);
bst.insert(9);
bst.insert(8);
bst.insert(10);
bst.insert(13);
bst.insert(12);
bst.insert(14);
bst.insert(20);
bst.insert(18);
bst.insert(25);
bst.insert(6);
// 测试前序遍历结果
let resultString = "";
bst.preOrderTraversal(function (key) {
resultString += key + " ";
});
console.log("✅ ~ 先序遍历resultString:", resultString); // 11 7 5 3 6 9 8 10 15 13 12 14 20 18 25
resultString = "";
bst.inOrderTraversal(function (key) {
resultString += key + " ";
});
console.log("✅ ~ 中序遍历resultString:", resultString); // 3 5 6 7 8 9 10 11 12 13 14 15 18 20 25
resultString = "";
bst.postOrderTraversal(function (key) {
resultString += key + " ";
});
console.log("✅ ~ 后序遍历resultString:", resultString); // 3 6 5 8 10 9 7 12 14 13 18 25 20 15 11
// 获取最值
console.log("✅ ~ bst.min():", bst.min());
console.log("✅ ~ bst.max():", bst.max());
// 查找特定的值
console.log("✅ ~ bst.search(10):", bst.search(10));
console.log("✅ ~ bst.search(21):", bst.search(21));
// 查找特定的值
console.log("✅ ~ bst.searchV2(10):", bst.searchV2(10));
console.log("✅ ~ bst.searchV2(21):", bst.searchV2(21));
// 删除测试
console.log("✅ ~ bst.searchV2(10):", bst.remove(7));
resultString = "";
bst.postOrderTraversal(function (key) {
resultString += key + " ";
});
console.log("✅ ~ 后序遍历resultString:", resultString); // 3 6 5 8 10 9 7 12 14 13 18 25 20 15 11
排序算法
- 冒泡排序
- 计数排序
- 快速排序
- 归并排序
- 插入排序
- 选择排序