Merge branch 'krahets:master' into typescript
2
.github/pull_request_template.md
vendored
@ -4,4 +4,4 @@
|
|||||||
|
|
||||||
- [ ] I've tested the code and ensured the outputs are the same as the outputs of reference codes.
|
- [ ] I've tested the code and ensured the outputs are the same as the outputs of reference codes.
|
||||||
- [ ] I've checked the codes (formatting, comments, indentation, file header, etc) carefully.
|
- [ ] I've checked the codes (formatting, comments, indentation, file header, etc) carefully.
|
||||||
- [ ] The code is not relied on a particular environment or IDE and can be runned on a common system (Win, MacOS, Ubuntu).
|
- [ ] The code does not rely on a particular environment or IDE and can be executed on a standard system (Win, macOS, Ubuntu).
|
||||||
|
@ -59,19 +59,11 @@ public:
|
|||||||
|
|
||||||
/* 访问队首元素 */
|
/* 访问队首元素 */
|
||||||
int peek() {
|
int peek() {
|
||||||
// 删除头结点
|
|
||||||
if (empty())
|
if (empty())
|
||||||
throw out_of_range("队列为空");
|
throw out_of_range("队列为空");
|
||||||
return nums[front];
|
return nums[front];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 访问指定索引元素 */
|
|
||||||
int get(int index) {
|
|
||||||
if (index >= size())
|
|
||||||
throw out_of_range("索引越界");
|
|
||||||
return nums[(front + index) % capacity()];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 将数组转化为 Vector 并返回 */
|
/* 将数组转化为 Vector 并返回 */
|
||||||
vector<int> toVector() {
|
vector<int> toVector() {
|
||||||
int siz = size();
|
int siz = size();
|
||||||
@ -105,10 +97,6 @@ int main() {
|
|||||||
int peek = queue->peek();
|
int peek = queue->peek();
|
||||||
cout << "队首元素 peek = " << peek << endl;
|
cout << "队首元素 peek = " << peek << endl;
|
||||||
|
|
||||||
/* 访问指定索引元素 */
|
|
||||||
int num = queue->get(2);
|
|
||||||
cout << "队列第 3 个元素为 num = " << num << endl;
|
|
||||||
|
|
||||||
/* 元素出队 */
|
/* 元素出队 */
|
||||||
int poll = queue->poll();
|
int poll = queue->poll();
|
||||||
cout << "出队元素 poll = " << poll << ",出队后 queue = ";
|
cout << "出队元素 poll = " << poll << ",出队后 queue = ";
|
||||||
|
@ -41,13 +41,6 @@ public:
|
|||||||
return stack.back();
|
return stack.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 访问索引 index 处元素 */
|
|
||||||
int get(int index) {
|
|
||||||
if(index >= size())
|
|
||||||
throw out_of_range("索引越界");
|
|
||||||
return stack[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 返回 Vector */
|
/* 返回 Vector */
|
||||||
vector<int> toVector() {
|
vector<int> toVector() {
|
||||||
return stack;
|
return stack;
|
||||||
@ -73,10 +66,6 @@ int main() {
|
|||||||
int top = stack->top();
|
int top = stack->top();
|
||||||
cout << "栈顶元素 top = " << top << endl;
|
cout << "栈顶元素 top = " << top << endl;
|
||||||
|
|
||||||
/* 访问索引 index 处元素 */
|
|
||||||
int num = stack->get(3);
|
|
||||||
cout << "栈索引 3 处的元素为 num = " << num << endl;
|
|
||||||
|
|
||||||
/* 元素出栈 */
|
/* 元素出栈 */
|
||||||
int pop = stack->pop();
|
int pop = stack->pop();
|
||||||
cout << "出栈元素 pop = " << pop << ",出栈后 stack = ";
|
cout << "出栈元素 pop = " << pop << ",出栈后 stack = ";
|
||||||
|
@ -58,19 +58,11 @@ class ArrayQueue {
|
|||||||
|
|
||||||
/* 访问队首元素 */
|
/* 访问队首元素 */
|
||||||
public int peek() {
|
public int peek() {
|
||||||
// 删除头结点
|
|
||||||
if (isEmpty())
|
if (isEmpty())
|
||||||
throw new EmptyStackException();
|
throw new EmptyStackException();
|
||||||
return nums[front];
|
return nums[front];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 访问索引 index 处元素 */
|
|
||||||
int get(int index) {
|
|
||||||
if (index >= size())
|
|
||||||
throw new IndexOutOfBoundsException();
|
|
||||||
return nums[(front + index) % capacity()];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 返回数组 */
|
/* 返回数组 */
|
||||||
public int[] toArray() {
|
public int[] toArray() {
|
||||||
int size = size();
|
int size = size();
|
||||||
|
@ -45,13 +45,6 @@ class ArrayStack {
|
|||||||
return stack.get(size() - 1);
|
return stack.get(size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 访问索引 index 处元素 */
|
|
||||||
public int get(int index) {
|
|
||||||
if (index >= size())
|
|
||||||
throw new IndexOutOfBoundsException();
|
|
||||||
return stack.get(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 将 List 转化为 Array 并返回 */
|
/* 将 List 转化为 Array 并返回 */
|
||||||
public Object[] toArray() {
|
public Object[] toArray() {
|
||||||
return stack.toArray();
|
return stack.toArray();
|
||||||
@ -75,10 +68,6 @@ public class array_stack {
|
|||||||
int peek = stack.peek();
|
int peek = stack.peek();
|
||||||
System.out.println("栈顶元素 peek = " + peek);
|
System.out.println("栈顶元素 peek = " + peek);
|
||||||
|
|
||||||
/* 访问索引 index 处元素 */
|
|
||||||
int num = stack.get(3);
|
|
||||||
System.out.println("栈索引 3 处的元素为 num = " + num);
|
|
||||||
|
|
||||||
/* 元素出栈 */
|
/* 元素出栈 */
|
||||||
int pop = stack.pop();
|
int pop = stack.pop();
|
||||||
System.out.println("出栈元素 pop = " + pop + ",出栈后 stack = " + Arrays.toString(stack.toArray()));
|
System.out.println("出栈元素 pop = " + pop + ",出栈后 stack = " + Arrays.toString(stack.toArray()));
|
||||||
|
110
codes/javascript/chapter_stack_and_queue/array_queue.js
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
/**
|
||||||
|
* File: array_queue.js
|
||||||
|
* Created Time: 2022-12-13
|
||||||
|
* Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com)
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/* 基于环形数组实现的队列 */
|
||||||
|
class ArrayQueue {
|
||||||
|
#queue; // 用于存储队列元素的数组
|
||||||
|
#front = 0; // 头指针,指向队首
|
||||||
|
#rear = 0; // 尾指针,指向队尾 + 1
|
||||||
|
|
||||||
|
constructor(capacity) {
|
||||||
|
this.#queue = new Array(capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 获取队列的容量 */
|
||||||
|
get capacity() {
|
||||||
|
return this.#queue.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 获取队列的长度 */
|
||||||
|
get size() {
|
||||||
|
// 由于将数组看作为环形,可能 rear < front ,因此需要取余数
|
||||||
|
return (this.capacity + this.#rear - this.#front) % this.capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 判断队列是否为空 */
|
||||||
|
empty() {
|
||||||
|
return this.#rear - this.#front == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 入队 */
|
||||||
|
offer(num) {
|
||||||
|
if (this.size == this.capacity)
|
||||||
|
throw new Error("队列已满");
|
||||||
|
// 尾结点后添加 num
|
||||||
|
this.#queue[this.#rear] = num;
|
||||||
|
// 尾指针向后移动一位,越过尾部后返回到数组头部
|
||||||
|
this.#rear = (this.#rear + 1) % this.capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 出队 */
|
||||||
|
poll() {
|
||||||
|
const num = this.peek();
|
||||||
|
// 队头指针向后移动一位,若越过尾部则返回到数组头部
|
||||||
|
this.#front = (this.#front + 1) % this.capacity;
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 访问队首元素 */
|
||||||
|
peek() {
|
||||||
|
if (this.empty())
|
||||||
|
throw new Error("队列为空");
|
||||||
|
return this.#queue[this.#front];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 返回 Array */
|
||||||
|
toArray() {
|
||||||
|
const siz = this.size;
|
||||||
|
const cap = this.capacity;
|
||||||
|
// 仅转换有效长度范围内的列表元素
|
||||||
|
const arr = new Array(siz);
|
||||||
|
for (let i = 0, j = this.#front; i < siz; i++, j++) {
|
||||||
|
arr[i] = this.#queue[j % cap];
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Driver Code */
|
||||||
|
/* 初始化队列 */
|
||||||
|
const capacity = 10;
|
||||||
|
const queue = new ArrayQueue(capacity);
|
||||||
|
|
||||||
|
/* 元素入队 */
|
||||||
|
queue.offer(1);
|
||||||
|
queue.offer(3);
|
||||||
|
queue.offer(2);
|
||||||
|
queue.offer(5);
|
||||||
|
queue.offer(4);
|
||||||
|
console.log("队列 queue = ");
|
||||||
|
console.log(queue.toArray());
|
||||||
|
|
||||||
|
/* 访问队首元素 */
|
||||||
|
const peek = queue.peek();
|
||||||
|
console.log("队首元素 peek = " + peek);
|
||||||
|
|
||||||
|
/* 元素出队 */
|
||||||
|
const poll = queue.poll();
|
||||||
|
console.log("出队元素 poll = " + poll + ",出队后 queue = ");
|
||||||
|
console.log(queue.toArray());
|
||||||
|
|
||||||
|
/* 获取队列的长度 */
|
||||||
|
const size = queue.size;
|
||||||
|
console.log("队列长度 size = " + size);
|
||||||
|
|
||||||
|
/* 判断队列是否为空 */
|
||||||
|
const empty = queue.empty();
|
||||||
|
console.log("队列是否为空 = " + empty);
|
||||||
|
|
||||||
|
/* 测试环形数组 */
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
queue.offer(i);
|
||||||
|
queue.poll();
|
||||||
|
console.log("第 " + i + " 轮入队 + 出队后 queue = ");
|
||||||
|
console.log(queue.toArray());
|
||||||
|
}
|
@ -11,6 +11,7 @@ class ArrayStack {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.stack = [];
|
this.stack = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 获取栈的长度 */
|
/* 获取栈的长度 */
|
||||||
get size() {
|
get size() {
|
||||||
return this.stack.length;
|
return this.stack.length;
|
||||||
@ -28,22 +29,18 @@ class ArrayStack {
|
|||||||
|
|
||||||
/* 出栈 */
|
/* 出栈 */
|
||||||
pop() {
|
pop() {
|
||||||
if (this.empty()) throw "栈为空";
|
if (this.empty())
|
||||||
|
throw new Error("栈为空");
|
||||||
return this.stack.pop();
|
return this.stack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 访问栈顶元素 */
|
/* 访问栈顶元素 */
|
||||||
top() {
|
top() {
|
||||||
if (this.empty()) throw "栈为空";
|
if (this.empty())
|
||||||
|
throw new Error("栈为空");
|
||||||
return this.stack[this.stack.length - 1];
|
return this.stack[this.stack.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 访问索引 index 处元素 */
|
|
||||||
get(index) {
|
|
||||||
if (index >= this.size) throw "索引越界";
|
|
||||||
return this.stack[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 返回 Array */
|
/* 返回 Array */
|
||||||
toArray() {
|
toArray() {
|
||||||
return this.stack;
|
return this.stack;
|
||||||
@ -69,10 +66,6 @@ console.log(stack.toArray());
|
|||||||
const top = stack.top();
|
const top = stack.top();
|
||||||
console.log("栈顶元素 top = " + top);
|
console.log("栈顶元素 top = " + top);
|
||||||
|
|
||||||
/* 访问索引 index 处元素 */
|
|
||||||
const num = stack.get(3);
|
|
||||||
console.log("栈索引 3 处的元素为 num = " + num);
|
|
||||||
|
|
||||||
/* 元素出栈 */
|
/* 元素出栈 */
|
||||||
const pop = stack.pop();
|
const pop = stack.pop();
|
||||||
console.log("出栈元素 pop = " + pop + ",出栈后 stack = ");
|
console.log("出栈元素 pop = " + pop + ",出栈后 stack = ");
|
||||||
|
102
codes/javascript/chapter_stack_and_queue/linkedlist_queue.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/**
|
||||||
|
* File: linkedlist_queue.js
|
||||||
|
* Created Time: 2022-12-20
|
||||||
|
* Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const ListNode = require("../include/ListNode");
|
||||||
|
|
||||||
|
/* 基于链表实现的队列 */
|
||||||
|
class LinkedListQueue {
|
||||||
|
#front; // 头结点 #front
|
||||||
|
#rear; // 尾结点 #rear
|
||||||
|
#queSize = 0;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.#front = null;
|
||||||
|
this.#rear = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 获取队列的长度 */
|
||||||
|
get size() {
|
||||||
|
return this.#queSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 判断队列是否为空 */
|
||||||
|
isEmpty() {
|
||||||
|
return this.size === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 入队 */
|
||||||
|
offer(num) {
|
||||||
|
// 尾结点后添加 num
|
||||||
|
const node = new ListNode(num);
|
||||||
|
// 如果队列为空,则令头、尾结点都指向该结点
|
||||||
|
if (!this.#front) {
|
||||||
|
this.#front = node;
|
||||||
|
this.#rear = node;
|
||||||
|
// 如果队列不为空,则将该结点添加到尾结点后
|
||||||
|
} else {
|
||||||
|
this.#rear.next = node;
|
||||||
|
this.#rear = node;
|
||||||
|
}
|
||||||
|
this.#queSize++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 出队 */
|
||||||
|
poll() {
|
||||||
|
const num = this.peek();
|
||||||
|
// 删除头结点
|
||||||
|
this.#front = this.#front.next;
|
||||||
|
this.#queSize--;
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 访问队首元素 */
|
||||||
|
peek() {
|
||||||
|
if (this.size === 0)
|
||||||
|
throw new Error("队列为空");
|
||||||
|
return this.#front.val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 将链表转化为 Array 并返回 */
|
||||||
|
toArray() {
|
||||||
|
let node = this.#front;
|
||||||
|
const res = new Array(this.size);
|
||||||
|
for (let i = 0; i < res.length; i++) {
|
||||||
|
res[i] = node.val;
|
||||||
|
node = node.next;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Driver Code */
|
||||||
|
/* 初始化队列 */
|
||||||
|
const queue = new LinkedListQueue();
|
||||||
|
|
||||||
|
/* 元素入队 */
|
||||||
|
queue.offer(1);
|
||||||
|
queue.offer(3);
|
||||||
|
queue.offer(2);
|
||||||
|
queue.offer(5);
|
||||||
|
queue.offer(4);
|
||||||
|
console.log("队列 queue = " + queue.toArray());
|
||||||
|
|
||||||
|
/* 访问队首元素 */
|
||||||
|
const peek = queue.peek();
|
||||||
|
console.log("队首元素 peek = " + peek);
|
||||||
|
|
||||||
|
/* 元素出队 */
|
||||||
|
const poll = queue.poll();
|
||||||
|
console.log("出队元素 poll = " + poll + ",出队后 queue = " + queue.toArray());
|
||||||
|
|
||||||
|
/* 获取队列的长度 */
|
||||||
|
const size = queue.size;
|
||||||
|
console.log("队列长度 size = " + size);
|
||||||
|
|
||||||
|
/* 判断队列是否为空 */
|
||||||
|
const isEmpty = queue.isEmpty();
|
||||||
|
console.log("队列是否为空 = " + isEmpty);
|
@ -4,9 +4,10 @@
|
|||||||
* Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com)
|
* Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/* Driver Code */
|
||||||
/* 初始化队列 */
|
/* 初始化队列 */
|
||||||
// JavaScript 没有内置的队列,可以把 Array 当作队列来使用
|
// JavaScript 没有内置的队列,可以把 Array 当作队列来使用
|
||||||
// 注意:由于是数组,所以 shift() 的时间复杂度是 O(n)
|
|
||||||
const queue = [];
|
const queue = [];
|
||||||
|
|
||||||
/* 元素入队 */
|
/* 元素入队 */
|
||||||
@ -20,7 +21,7 @@ queue.push(4);
|
|||||||
const peek = queue[0];
|
const peek = queue[0];
|
||||||
|
|
||||||
/* 元素出队 */
|
/* 元素出队 */
|
||||||
// O(n)
|
// 底层是数组,因此 shift() 方法的时间复杂度为 O(n)
|
||||||
const poll = queue.shift();
|
const poll = queue.shift();
|
||||||
|
|
||||||
/* 获取队列的长度 */
|
/* 获取队列的长度 */
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
* Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com)
|
* Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/* Driver Code */
|
||||||
/* 初始化栈 */
|
/* 初始化栈 */
|
||||||
// Javascript 没有内置的栈类,可以把 Array 当作栈来使用
|
// Javascript 没有内置的栈类,可以把 Array 当作栈来使用
|
||||||
const stack = [];
|
const stack = [];
|
||||||
|
@ -42,7 +42,6 @@ class ArrayQueue:
|
|||||||
|
|
||||||
""" 出队 """
|
""" 出队 """
|
||||||
def poll(self):
|
def poll(self):
|
||||||
# 删除头结点
|
|
||||||
num = self.peek()
|
num = self.peek()
|
||||||
# 队头指针向后移动一位,若越过尾部则返回到数组头部
|
# 队头指针向后移动一位,若越过尾部则返回到数组头部
|
||||||
self.__front = (self.__front + 1) % self.capacity()
|
self.__front = (self.__front + 1) % self.capacity()
|
||||||
@ -50,19 +49,11 @@ class ArrayQueue:
|
|||||||
|
|
||||||
""" 访问队首元素 """
|
""" 访问队首元素 """
|
||||||
def peek(self):
|
def peek(self):
|
||||||
# 删除头结点
|
|
||||||
if self.is_empty():
|
if self.is_empty():
|
||||||
print("队列为空")
|
print("队列为空")
|
||||||
return False
|
return False
|
||||||
return self.__nums[self.__front]
|
return self.__nums[self.__front]
|
||||||
|
|
||||||
""" 访问指定位置元素 """
|
|
||||||
def get(self, index):
|
|
||||||
if index >= self.size():
|
|
||||||
print("索引越界")
|
|
||||||
return False
|
|
||||||
return self.__nums[(self.__front + index) % self.capacity()]
|
|
||||||
|
|
||||||
""" 返回列表用于打印 """
|
""" 返回列表用于打印 """
|
||||||
def to_list(self):
|
def to_list(self):
|
||||||
res = [0] * self.size()
|
res = [0] * self.size()
|
||||||
@ -90,10 +81,6 @@ if __name__ == "__main__":
|
|||||||
peek = queue.peek()
|
peek = queue.peek()
|
||||||
print("队首元素 peek =", peek)
|
print("队首元素 peek =", peek)
|
||||||
|
|
||||||
""" 访问索引 index 处元素 """
|
|
||||||
num = queue.get(3)
|
|
||||||
print("队列索引 3 处的元素为 num =", num)
|
|
||||||
|
|
||||||
""" 元素出队 """
|
""" 元素出队 """
|
||||||
poll = queue.poll()
|
poll = queue.poll()
|
||||||
print("出队元素 poll =", poll)
|
print("出队元素 poll =", poll)
|
||||||
|
@ -35,11 +35,6 @@ class ArrayStack:
|
|||||||
assert not self.is_empty(), "栈为空"
|
assert not self.is_empty(), "栈为空"
|
||||||
return self.__stack[-1]
|
return self.__stack[-1]
|
||||||
|
|
||||||
""" 访问索引 index 处元素 """
|
|
||||||
def get(self, index):
|
|
||||||
assert index < self.size(), "索引越界"
|
|
||||||
return self.__stack[index]
|
|
||||||
|
|
||||||
""" 返回列表用于打印 """
|
""" 返回列表用于打印 """
|
||||||
def to_list(self):
|
def to_list(self):
|
||||||
return self.__stack
|
return self.__stack
|
||||||
@ -62,10 +57,6 @@ if __name__ == "__main__":
|
|||||||
peek = stack.peek()
|
peek = stack.peek()
|
||||||
print("栈顶元素 peek =", peek)
|
print("栈顶元素 peek =", peek)
|
||||||
|
|
||||||
""" 访问索引 index 处元素 """
|
|
||||||
num = stack.get(3)
|
|
||||||
print("栈索引 3 处的元素为 num =", num)
|
|
||||||
|
|
||||||
""" 元素出栈 """
|
""" 元素出栈 """
|
||||||
pop = stack.pop()
|
pop = stack.pop()
|
||||||
print("出栈元素 pop =", pop)
|
print("出栈元素 pop =", pop)
|
||||||
|
111
codes/typescript/chapter_stack_and_queue/array_queue.ts
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/**
|
||||||
|
* File: array_queue.ts
|
||||||
|
* Created Time: 2022-12-11
|
||||||
|
* Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com)
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/* 基于环形数组实现的队列 */
|
||||||
|
class ArrayQueue {
|
||||||
|
private queue: number[]; // 用于存储队列元素的数组
|
||||||
|
private front: number = 0; // 头指针,指向队首
|
||||||
|
private rear: number = 0; // 尾指针,指向队尾 + 1
|
||||||
|
private CAPACITY: number = 1e5;
|
||||||
|
|
||||||
|
constructor(capacity?: number) {
|
||||||
|
this.queue = new Array<number>(capacity ?? this.CAPACITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 获取队列的容量 */
|
||||||
|
get capacity(): number {
|
||||||
|
return this.queue.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 获取队列的长度 */
|
||||||
|
get size(): number {
|
||||||
|
// 由于将数组看作为环形,可能 rear < front ,因此需要取余数
|
||||||
|
return (this.capacity + this.rear - this.front) % this.capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 判断队列是否为空 */
|
||||||
|
empty(): boolean {
|
||||||
|
return this.rear - this.front == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 入队 */
|
||||||
|
offer(num: number): void {
|
||||||
|
if (this.size == this.capacity)
|
||||||
|
throw new Error("队列已满");
|
||||||
|
// 尾结点后添加 num
|
||||||
|
this.queue[this.rear] = num;
|
||||||
|
// 尾指针向后移动一位,越过尾部后返回到数组头部
|
||||||
|
this.rear = (this.rear + 1) % this.capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 出队 */
|
||||||
|
poll(): number {
|
||||||
|
const num = this.peek();
|
||||||
|
// 队头指针向后移动一位,若越过尾部则返回到数组头部
|
||||||
|
this.front = (this.front + 1) % this.capacity;
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 访问队首元素 */
|
||||||
|
peek(): number {
|
||||||
|
if (this.empty())
|
||||||
|
throw new Error("队列为空");
|
||||||
|
return this.queue[this.front];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 返回 Array */
|
||||||
|
toArray(): number[] {
|
||||||
|
const siz = this.size;
|
||||||
|
const cap = this.capacity;
|
||||||
|
// 仅转换有效长度范围内的列表元素
|
||||||
|
const arr = new Array(siz);
|
||||||
|
for (let i = 0, j = this.front; i < siz; i++, j++) {
|
||||||
|
arr[i] = this.queue[j % cap];
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 初始化队列 */
|
||||||
|
const capacity = 10;
|
||||||
|
const queue = new ArrayQueue(capacity);
|
||||||
|
|
||||||
|
/* 元素入队 */
|
||||||
|
queue.offer(1);
|
||||||
|
queue.offer(3);
|
||||||
|
queue.offer(2);
|
||||||
|
queue.offer(5);
|
||||||
|
queue.offer(4);
|
||||||
|
console.log("队列 queue = ");
|
||||||
|
console.log(queue.toArray());
|
||||||
|
|
||||||
|
/* 访问队首元素 */
|
||||||
|
const peek = queue.peek();
|
||||||
|
console.log("队首元素 peek = " + peek);
|
||||||
|
|
||||||
|
/* 元素出队 */
|
||||||
|
const poll = queue.poll();
|
||||||
|
console.log("出队元素 poll = " + poll + ",出队后 queue = ");
|
||||||
|
console.log(queue.toArray());
|
||||||
|
|
||||||
|
/* 获取队列的长度 */
|
||||||
|
const size = queue.size;
|
||||||
|
console.log("队列长度 size = " + size);
|
||||||
|
|
||||||
|
/* 判断队列是否为空 */
|
||||||
|
const empty = queue.empty();
|
||||||
|
console.log("队列是否为空 = " + empty);
|
||||||
|
|
||||||
|
/* 测试环形数组 */
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
queue.offer(i);
|
||||||
|
queue.poll();
|
||||||
|
console.log("第 " + i + " 轮入队 + 出队后 queue = ");
|
||||||
|
console.log(queue.toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
export { };
|
@ -29,22 +29,18 @@ class ArrayStack {
|
|||||||
|
|
||||||
/* 出栈 */
|
/* 出栈 */
|
||||||
pop(): number | undefined {
|
pop(): number | undefined {
|
||||||
if (this.empty()) throw new Error('栈为空');
|
if (this.empty())
|
||||||
|
throw new Error('栈为空');
|
||||||
return this.stack.pop();
|
return this.stack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 访问栈顶元素 */
|
/* 访问栈顶元素 */
|
||||||
top(): number | undefined {
|
top(): number | undefined {
|
||||||
if (this.empty()) throw new Error('栈为空');
|
if (this.empty())
|
||||||
|
throw new Error('栈为空');
|
||||||
return this.stack[this.stack.length - 1];
|
return this.stack[this.stack.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 访问索引 index 处元素 */
|
|
||||||
get(index: number): number | undefined {
|
|
||||||
if (index >= this.size) throw new Error('索引越界');
|
|
||||||
return this.stack[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 返回 Array */
|
/* 返回 Array */
|
||||||
toArray() {
|
toArray() {
|
||||||
return this.stack;
|
return this.stack;
|
||||||
@ -70,10 +66,6 @@ console.log(stack.toArray());
|
|||||||
const top = stack.top();
|
const top = stack.top();
|
||||||
console.log("栈顶元素 top = " + top);
|
console.log("栈顶元素 top = " + top);
|
||||||
|
|
||||||
/* 访问索引 index 处元素 */
|
|
||||||
const num = stack.get(3);
|
|
||||||
console.log("栈索引 3 处的元素为 num = " + num);
|
|
||||||
|
|
||||||
/* 元素出栈 */
|
/* 元素出栈 */
|
||||||
const pop = stack.pop();
|
const pop = stack.pop();
|
||||||
console.log("出栈元素 pop = " + pop + ",出栈后 stack = ");
|
console.log("出栈元素 pop = " + pop + ",出栈后 stack = ");
|
||||||
|
102
codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/**
|
||||||
|
* File: linkedlist_queue.ts
|
||||||
|
* Created Time: 2022-12-19
|
||||||
|
* Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ListNode from "../module/ListNode"
|
||||||
|
|
||||||
|
/* 基于链表实现的队列 */
|
||||||
|
class LinkedListQueue {
|
||||||
|
private front: ListNode | null; // 头结点 front
|
||||||
|
private rear: ListNode | null; // 尾结点 rear
|
||||||
|
private queSize: number = 0;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.front = null;
|
||||||
|
this.rear = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 获取队列的长度 */
|
||||||
|
get size(): number {
|
||||||
|
return this.queSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 判断队列是否为空 */
|
||||||
|
isEmpty(): boolean {
|
||||||
|
return this.size === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 入队 */
|
||||||
|
offer(num: number): void {
|
||||||
|
// 尾结点后添加 num
|
||||||
|
const node = new ListNode(num);
|
||||||
|
// 如果队列为空,则令头、尾结点都指向该结点
|
||||||
|
if (!this.front) {
|
||||||
|
this.front = node;
|
||||||
|
this.rear = node;
|
||||||
|
// 如果队列不为空,则将该结点添加到尾结点后
|
||||||
|
} else {
|
||||||
|
this.rear!.next = node;
|
||||||
|
this.rear = node;
|
||||||
|
}
|
||||||
|
this.queSize++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 出队 */
|
||||||
|
poll(): number {
|
||||||
|
const num = this.peek();
|
||||||
|
if (!this.front)
|
||||||
|
throw new Error("队列为空")
|
||||||
|
// 删除头结点
|
||||||
|
this.front = this.front.next;
|
||||||
|
this.queSize--;
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 访问队首元素 */
|
||||||
|
peek(): number {
|
||||||
|
if (this.size === 0)
|
||||||
|
throw new Error("队列为空");
|
||||||
|
return this.front!.val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 将链表转化为 Array 并返回 */
|
||||||
|
toArray(): number[] {
|
||||||
|
let node = this.front;
|
||||||
|
const res = new Array<number>(this.size);
|
||||||
|
for (let i = 0; i < res.length; i++) {
|
||||||
|
res[i] = node!.val;
|
||||||
|
node = node!.next;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 初始化队列 */
|
||||||
|
const queue = new LinkedListQueue();
|
||||||
|
|
||||||
|
/* 元素入队 */
|
||||||
|
queue.offer(1);
|
||||||
|
queue.offer(3);
|
||||||
|
queue.offer(2);
|
||||||
|
queue.offer(5);
|
||||||
|
queue.offer(4);
|
||||||
|
console.log("队列 queue = " + queue.toArray());
|
||||||
|
|
||||||
|
/* 访问队首元素 */
|
||||||
|
const peek = queue.peek();
|
||||||
|
console.log("队首元素 peek = " + peek);
|
||||||
|
|
||||||
|
/* 元素出队 */
|
||||||
|
const poll = queue.poll();
|
||||||
|
console.log("出队元素 poll = " + poll + ",出队后 queue = " + queue.toArray());
|
||||||
|
|
||||||
|
/* 获取队列的长度 */
|
||||||
|
const size = queue.size;
|
||||||
|
console.log("队列长度 size = " + size);
|
||||||
|
|
||||||
|
/* 判断队列是否为空 */
|
||||||
|
const isEmpty = queue.isEmpty();
|
||||||
|
console.log("队列是否为空 = " + isEmpty);
|
@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
/* 初始化队列 */
|
/* 初始化队列 */
|
||||||
// TypeScript 没有内置的队列,可以把 Array 当作队列来使用
|
// TypeScript 没有内置的队列,可以把 Array 当作队列来使用
|
||||||
// 注意:由于是数组,所以 shift() 的时间复杂度是 O(n)
|
|
||||||
const queue: number[] = [];
|
const queue: number[] = [];
|
||||||
|
|
||||||
/* 元素入队 */
|
/* 元素入队 */
|
||||||
@ -20,7 +19,7 @@ queue.push(4);
|
|||||||
const peek = queue[0];
|
const peek = queue[0];
|
||||||
|
|
||||||
/* 元素出队 */
|
/* 元素出队 */
|
||||||
// O(n)
|
// 底层是数组,因此 shift() 方法的时间复杂度为 O(n)
|
||||||
const poll = queue.shift();
|
const poll = queue.shift();
|
||||||
|
|
||||||
/* 获取队列的长度 */
|
/* 获取队列的长度 */
|
||||||
|
@ -613,7 +613,7 @@ comments: true
|
|||||||
int val; // 结点值
|
int val; // 结点值
|
||||||
ListNode *next; // 指向后继结点的指针(引用)
|
ListNode *next; // 指向后继结点的指针(引用)
|
||||||
ListNode *prev; // 指向前驱结点的指针(引用)
|
ListNode *prev; // 指向前驱结点的指针(引用)
|
||||||
ListNode(int x) : val(x), next(nullptr) {} // 构造函数
|
ListNode(int x) : val(x), next(nullptr), prev(nullptr) {} // 构造函数
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -644,8 +644,8 @@ comments: true
|
|||||||
prev;
|
prev;
|
||||||
constructor(val, next) {
|
constructor(val, next) {
|
||||||
this.val = val === undefined ? 0 : val; // 结点值
|
this.val = val === undefined ? 0 : val; // 结点值
|
||||||
this.next = next === undefined ? null : next; // 指向后继结点的引用
|
this.next = next === undefined ? null : next; // 指向后继结点的指针(引用)
|
||||||
this.prev = prev === undefined ? null : prev; // 指向前驱结点的引用
|
this.prev = prev === undefined ? null : prev; // 指向前驱结点的指针(引用)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -660,8 +660,8 @@ comments: true
|
|||||||
prev: ListNode | null;
|
prev: ListNode | null;
|
||||||
constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) {
|
constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) {
|
||||||
this.val = val === undefined ? 0 : val; // 结点值
|
this.val = val === undefined ? 0 : val; // 结点值
|
||||||
this.next = next === undefined ? null : next; // 指向后继结点的引用
|
this.next = next === undefined ? null : next; // 指向后继结点的指针(引用)
|
||||||
this.prev = prev === undefined ? null : prev; // 指向前驱结点的引用
|
this.prev = prev === undefined ? null : prev; // 指向前驱结点的指针(引用)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -10,13 +10,15 @@ comments: true
|
|||||||
|
|
||||||
## 列表常用操作
|
## 列表常用操作
|
||||||
|
|
||||||
**初始化列表。** 我们通常使用 `Integer[]` 包装类和 `Arrays.asList()` 作为中转,来初始化一个带有初始值的列表。
|
**初始化列表。** 我们通常会使用到“无初始值”和“有初始值”的两种初始化方法。
|
||||||
|
|
||||||
=== "Java"
|
=== "Java"
|
||||||
|
|
||||||
```java title="list.java"
|
```java title="list.java"
|
||||||
/* 初始化列表 */
|
/* 初始化列表 */
|
||||||
// 注意数组的元素类型是 int[] 的包装类 Integer[]
|
// 无初始值
|
||||||
|
List<Integer> list1 = new ArrayList<>();
|
||||||
|
// 有初始值(注意数组的元素类型需为 int[] 的包装类 Integer[])
|
||||||
Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 };
|
Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 };
|
||||||
List<Integer> list = new ArrayList<>(Arrays.asList(numbers));
|
List<Integer> list = new ArrayList<>(Arrays.asList(numbers));
|
||||||
```
|
```
|
||||||
@ -25,6 +27,10 @@ comments: true
|
|||||||
|
|
||||||
```cpp title="list.cpp"
|
```cpp title="list.cpp"
|
||||||
/* 初始化列表 */
|
/* 初始化列表 */
|
||||||
|
// 需注意,C++ 中 vector 即是本文描述的 list
|
||||||
|
// 无初始值
|
||||||
|
vector<int> list1;
|
||||||
|
// 有初始值
|
||||||
vector<int> list = { 1, 3, 2, 5, 4 };
|
vector<int> list = { 1, 3, 2, 5, 4 };
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -32,6 +38,9 @@ comments: true
|
|||||||
|
|
||||||
```python title="list.py"
|
```python title="list.py"
|
||||||
""" 初始化列表 """
|
""" 初始化列表 """
|
||||||
|
# 无初始值
|
||||||
|
list1 = []
|
||||||
|
# 有初始值
|
||||||
list = [1, 3, 2, 5, 4]
|
list = [1, 3, 2, 5, 4]
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -39,6 +48,9 @@ comments: true
|
|||||||
|
|
||||||
```go title="list_test.go"
|
```go title="list_test.go"
|
||||||
/* 初始化列表 */
|
/* 初始化列表 */
|
||||||
|
// 无初始值
|
||||||
|
list1 := []int
|
||||||
|
// 有初始值
|
||||||
list := []int{1, 3, 2, 5, 4}
|
list := []int{1, 3, 2, 5, 4}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -46,6 +58,9 @@ comments: true
|
|||||||
|
|
||||||
```js title="list.js"
|
```js title="list.js"
|
||||||
/* 初始化列表 */
|
/* 初始化列表 */
|
||||||
|
// 无初始值
|
||||||
|
const list1 = [];
|
||||||
|
// 有初始值
|
||||||
const list = [1, 3, 2, 5, 4];
|
const list = [1, 3, 2, 5, 4];
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -53,6 +68,9 @@ comments: true
|
|||||||
|
|
||||||
```typescript title="list.ts"
|
```typescript title="list.ts"
|
||||||
/* 初始化列表 */
|
/* 初始化列表 */
|
||||||
|
// 无初始值
|
||||||
|
const list1: number[] = [];
|
||||||
|
// 有初始值
|
||||||
const list: number[] = [1, 3, 2, 5, 4];
|
const list: number[] = [1, 3, 2, 5, 4];
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -23,6 +23,6 @@ comments: true
|
|||||||
|
|
||||||
- 与时间复杂度的定义类似,「空间复杂度」统计算法占用空间随着数据量变大时的增长趋势。
|
- 与时间复杂度的定义类似,「空间复杂度」统计算法占用空间随着数据量变大时的增长趋势。
|
||||||
|
|
||||||
- 算法运行中相关内存空间可分为输入空间、暂存空间、输出空间。通常情况下,空间复杂度不计入输入空间。暂存空间可分为指令空间、数据空间、栈帧空间,其中栈帧空间一般在递归函数中才会影响到空间复杂度。
|
- 算法运行中相关内存空间可分为输入空间、暂存空间、输出空间。通常情况下,输入空间不计入空间复杂度计算。暂存空间可分为指令空间、数据空间、栈帧空间,其中栈帧空间一般在递归函数中才会影响到空间复杂度。
|
||||||
- 我们一般只关心「最差空间复杂度」,即统计算法在「最差输入数据」和「最差运行时间点」下的空间复杂度。
|
- 我们一般只关心「最差空间复杂度」,即统计算法在「最差输入数据」和「最差运行时间点」下的空间复杂度。
|
||||||
- 常见空间复杂度从小到大排列有 $O(1)$ , $O(\log n)$ , $O(n)$ , $O(n^2)$ , $O(2^n)$ 。
|
- 常见空间复杂度从小到大排列有 $O(1)$ , $O(\log n)$ , $O(n)$ , $O(n^2)$ , $O(2^n)$ 。
|
||||||
|
@ -6,7 +6,7 @@ comments: true
|
|||||||
|
|
||||||
哈希表通过建立「键 key」和「值 value」之间的映射,实现高效的元素查找。具体地,输入一个 key ,在哈希表中查询并获取 value ,时间复杂度为 $O(1)$ 。
|
哈希表通过建立「键 key」和「值 value」之间的映射,实现高效的元素查找。具体地,输入一个 key ,在哈希表中查询并获取 value ,时间复杂度为 $O(1)$ 。
|
||||||
|
|
||||||
例如,给定一个包含 $n$ 个学生的数据库,每个学生有 "姓名 `name` ” 和 “学号 `id` ” 两项数据,希望实现一个查询功能:**输入一个学号,返回对应的姓名**,则可以使用哈希表实现。
|
例如,给定一个包含 $n$ 个学生的数据库,每个学生有“姓名 `name` ”和“学号 `id` ”两项数据,希望实现一个查询功能:**输入一个学号,返回对应的姓名**,则可以使用哈希表实现。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
@ -145,7 +145,6 @@ comments: true
|
|||||||
```js title="queue.js"
|
```js title="queue.js"
|
||||||
/* 初始化队列 */
|
/* 初始化队列 */
|
||||||
// JavaScript 没有内置的队列,可以把 Array 当作队列来使用
|
// JavaScript 没有内置的队列,可以把 Array 当作队列来使用
|
||||||
// 注意:由于是数组,所以 shift() 的时间复杂度是 O(n)
|
|
||||||
const queue = [];
|
const queue = [];
|
||||||
|
|
||||||
/* 元素入队 */
|
/* 元素入队 */
|
||||||
@ -159,7 +158,7 @@ comments: true
|
|||||||
const peek = queue[0];
|
const peek = queue[0];
|
||||||
|
|
||||||
/* 元素出队 */
|
/* 元素出队 */
|
||||||
// O(n)
|
// 底层是数组,因此 shift() 方法的时间复杂度为 O(n)
|
||||||
const poll = queue.shift();
|
const poll = queue.shift();
|
||||||
|
|
||||||
/* 获取队列的长度 */
|
/* 获取队列的长度 */
|
||||||
@ -174,7 +173,6 @@ comments: true
|
|||||||
```typescript title="queue.ts"
|
```typescript title="queue.ts"
|
||||||
/* 初始化队列 */
|
/* 初始化队列 */
|
||||||
// TypeScript 没有内置的队列,可以把 Array 当作队列来使用
|
// TypeScript 没有内置的队列,可以把 Array 当作队列来使用
|
||||||
// 注意:由于是数组,所以 shift() 的时间复杂度是 O(n)
|
|
||||||
const queue: number[] = [];
|
const queue: number[] = [];
|
||||||
|
|
||||||
/* 元素入队 */
|
/* 元素入队 */
|
||||||
@ -188,7 +186,7 @@ comments: true
|
|||||||
const peek = queue[0];
|
const peek = queue[0];
|
||||||
|
|
||||||
/* 元素出队 */
|
/* 元素出队 */
|
||||||
// O(n)
|
// 底层是数组,因此 shift() 方法的时间复杂度为 O(n)
|
||||||
const poll = queue.shift();
|
const poll = queue.shift();
|
||||||
|
|
||||||
/* 获取队列的长度 */
|
/* 获取队列的长度 */
|
||||||
@ -382,19 +380,16 @@ comments: true
|
|||||||
// 使用内置包 list 来实现队列
|
// 使用内置包 list 来实现队列
|
||||||
data *list.List
|
data *list.List
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLinkedListQueue 初始化链表
|
// NewLinkedListQueue 初始化链表
|
||||||
func NewLinkedListQueue() *LinkedListQueue {
|
func NewLinkedListQueue() *LinkedListQueue {
|
||||||
return &LinkedListQueue{
|
return &LinkedListQueue{
|
||||||
data: list.New(),
|
data: list.New(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Offer 入队
|
// Offer 入队
|
||||||
func (s *LinkedListQueue) Offer(value any) {
|
func (s *LinkedListQueue) Offer(value any) {
|
||||||
s.data.PushBack(value)
|
s.data.PushBack(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poll 出队
|
// Poll 出队
|
||||||
func (s *LinkedListQueue) Poll() any {
|
func (s *LinkedListQueue) Poll() any {
|
||||||
if s.IsEmpty() {
|
if s.IsEmpty() {
|
||||||
@ -404,7 +399,6 @@ comments: true
|
|||||||
s.data.Remove(e)
|
s.data.Remove(e)
|
||||||
return e.Value
|
return e.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peek 访问队首元素
|
// Peek 访问队首元素
|
||||||
func (s *LinkedListQueue) Peek() any {
|
func (s *LinkedListQueue) Peek() any {
|
||||||
if s.IsEmpty() {
|
if s.IsEmpty() {
|
||||||
@ -413,12 +407,10 @@ comments: true
|
|||||||
e := s.data.Front()
|
e := s.data.Front()
|
||||||
return e.Value
|
return e.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size 获取队列的长度
|
// Size 获取队列的长度
|
||||||
func (s *LinkedListQueue) Size() int {
|
func (s *LinkedListQueue) Size() int {
|
||||||
return s.data.Len()
|
return s.data.Len()
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsEmpty 判断队列是否为空
|
// IsEmpty 判断队列是否为空
|
||||||
func (s *LinkedListQueue) IsEmpty() bool {
|
func (s *LinkedListQueue) IsEmpty() bool {
|
||||||
return s.data.Len() == 0
|
return s.data.Len() == 0
|
||||||
@ -428,13 +420,107 @@ comments: true
|
|||||||
=== "JavaScript"
|
=== "JavaScript"
|
||||||
|
|
||||||
```js title="linkedlist_queue.js"
|
```js title="linkedlist_queue.js"
|
||||||
|
/* 基于链表实现的队列 */
|
||||||
|
class LinkedListQueue {
|
||||||
|
#front; // 头结点 #front
|
||||||
|
#rear; // 尾结点 #rear
|
||||||
|
#queSize = 0;
|
||||||
|
constructor() {
|
||||||
|
this.#front = null;
|
||||||
|
this.#rear = null;
|
||||||
|
}
|
||||||
|
/* 获取队列的长度 */
|
||||||
|
get size() {
|
||||||
|
return this.#queSize;
|
||||||
|
}
|
||||||
|
/* 判断队列是否为空 */
|
||||||
|
isEmpty() {
|
||||||
|
return this.size === 0;
|
||||||
|
}
|
||||||
|
/* 入队 */
|
||||||
|
offer(num) {
|
||||||
|
// 尾结点后添加 num
|
||||||
|
const node = new ListNode(num);
|
||||||
|
// 如果队列为空,则令头、尾结点都指向该结点
|
||||||
|
if (!this.#front) {
|
||||||
|
this.#front = node;
|
||||||
|
this.#rear = node;
|
||||||
|
// 如果队列不为空,则将该结点添加到尾结点后
|
||||||
|
} else {
|
||||||
|
this.#rear.next = node;
|
||||||
|
this.#rear = node;
|
||||||
|
}
|
||||||
|
this.#queSize++;
|
||||||
|
}
|
||||||
|
/* 出队 */
|
||||||
|
poll() {
|
||||||
|
const num = this.peek();
|
||||||
|
// 删除头结点
|
||||||
|
this.#front = this.#front.next;
|
||||||
|
this.#queSize--;
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
/* 访问队首元素 */
|
||||||
|
peek() {
|
||||||
|
if (this.size === 0)
|
||||||
|
throw new Error("队列为空");
|
||||||
|
return this.#front.val;
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "TypeScript"
|
=== "TypeScript"
|
||||||
|
|
||||||
```typescript title="linkedlist_queue.ts"
|
```typescript title="linkedlist_queue.ts"
|
||||||
|
/* 基于链表实现的队列 */
|
||||||
|
class LinkedListQueue {
|
||||||
|
private front: ListNode | null; // 头结点 front
|
||||||
|
private rear: ListNode | null; // 尾结点 rear
|
||||||
|
private queSize: number = 0;
|
||||||
|
constructor() {
|
||||||
|
this.front = null;
|
||||||
|
this.rear = null;
|
||||||
|
}
|
||||||
|
/* 获取队列的长度 */
|
||||||
|
get size(): number {
|
||||||
|
return this.queSize;
|
||||||
|
}
|
||||||
|
/* 判断队列是否为空 */
|
||||||
|
isEmpty(): boolean {
|
||||||
|
return this.size === 0;
|
||||||
|
}
|
||||||
|
/* 入队 */
|
||||||
|
offer(num: number): void {
|
||||||
|
// 尾结点后添加 num
|
||||||
|
const node = new ListNode(num);
|
||||||
|
// 如果队列为空,则令头、尾结点都指向该结点
|
||||||
|
if (!this.front) {
|
||||||
|
this.front = node;
|
||||||
|
this.rear = node;
|
||||||
|
// 如果队列不为空,则将该结点添加到尾结点后
|
||||||
|
} else {
|
||||||
|
this.rear!.next = node;
|
||||||
|
this.rear = node;
|
||||||
|
}
|
||||||
|
this.queSize++;
|
||||||
|
}
|
||||||
|
/* 出队 */
|
||||||
|
poll(): number {
|
||||||
|
const num = this.peek();
|
||||||
|
if (!this.front)
|
||||||
|
throw new Error("队列为空")
|
||||||
|
// 删除头结点
|
||||||
|
this.front = this.front.next;
|
||||||
|
this.queSize--;
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
/* 访问队首元素 */
|
||||||
|
peek(): number {
|
||||||
|
if (this.size === 0)
|
||||||
|
throw new Error("队列为空");
|
||||||
|
return this.front!.val;
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "C"
|
=== "C"
|
||||||
@ -506,17 +592,10 @@ comments: true
|
|||||||
}
|
}
|
||||||
/* 访问队首元素 */
|
/* 访问队首元素 */
|
||||||
public int peek() {
|
public int peek() {
|
||||||
// 删除头结点
|
|
||||||
if (isEmpty())
|
if (isEmpty())
|
||||||
throw new EmptyStackException();
|
throw new EmptyStackException();
|
||||||
return nums[front];
|
return nums[front];
|
||||||
}
|
}
|
||||||
/* 访问指定索引元素 */
|
|
||||||
int get(int index) {
|
|
||||||
if (index >= size())
|
|
||||||
throw new IndexOutOfBoundsException();
|
|
||||||
return nums[(front + index) % capacity()];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -570,17 +649,10 @@ comments: true
|
|||||||
}
|
}
|
||||||
/* 访问队首元素 */
|
/* 访问队首元素 */
|
||||||
int peek() {
|
int peek() {
|
||||||
// 删除头结点
|
|
||||||
if (empty())
|
if (empty())
|
||||||
throw out_of_range("队列为空");
|
throw out_of_range("队列为空");
|
||||||
return nums[front];
|
return nums[front];
|
||||||
}
|
}
|
||||||
/* 访问指定位置元素 */
|
|
||||||
int get(int index) {
|
|
||||||
if (index >= size())
|
|
||||||
throw out_of_range("索引越界");
|
|
||||||
return nums[(front + index) % capacity()]
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -619,7 +691,6 @@ comments: true
|
|||||||
|
|
||||||
""" 出队 """
|
""" 出队 """
|
||||||
def poll(self):
|
def poll(self):
|
||||||
# 删除头结点
|
|
||||||
num = self.peek()
|
num = self.peek()
|
||||||
# 队头指针向后移动一位,若越过尾部则返回到数组头部
|
# 队头指针向后移动一位,若越过尾部则返回到数组头部
|
||||||
self.__front = (self.__front + 1) % self.capacity()
|
self.__front = (self.__front + 1) % self.capacity()
|
||||||
@ -627,19 +698,11 @@ comments: true
|
|||||||
|
|
||||||
""" 访问队首元素 """
|
""" 访问队首元素 """
|
||||||
def peek(self):
|
def peek(self):
|
||||||
# 删除头结点
|
|
||||||
if self.is_empty():
|
if self.is_empty():
|
||||||
print("队列为空")
|
print("队列为空")
|
||||||
return False
|
return False
|
||||||
return self.__nums[self.__front]
|
return self.__nums[self.__front]
|
||||||
|
|
||||||
""" 访问指定位置元素 """
|
|
||||||
def get(self, index):
|
|
||||||
if index >= self.size():
|
|
||||||
print("索引越界")
|
|
||||||
return False
|
|
||||||
return self.__nums[(self.__front + index) % self.capacity()]
|
|
||||||
|
|
||||||
""" 返回列表用于打印 """
|
""" 返回列表用于打印 """
|
||||||
def to_list(self):
|
def to_list(self):
|
||||||
res = [0] * self.size()
|
res = [0] * self.size()
|
||||||
@ -660,7 +723,6 @@ comments: true
|
|||||||
front int // 头指针,指向队首
|
front int // 头指针,指向队首
|
||||||
rear int // 尾指针,指向队尾 + 1
|
rear int // 尾指针,指向队尾 + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewArrayQueue 基于环形数组实现的队列
|
// NewArrayQueue 基于环形数组实现的队列
|
||||||
func NewArrayQueue(capacity int) *ArrayQueue {
|
func NewArrayQueue(capacity int) *ArrayQueue {
|
||||||
return &ArrayQueue{
|
return &ArrayQueue{
|
||||||
@ -670,18 +732,15 @@ comments: true
|
|||||||
rear: 0,
|
rear: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size 获取队列的长度
|
// Size 获取队列的长度
|
||||||
func (q *ArrayQueue) Size() int {
|
func (q *ArrayQueue) Size() int {
|
||||||
size := (q.capacity + q.rear - q.front) % q.capacity
|
size := (q.capacity + q.rear - q.front) % q.capacity
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsEmpty 判断队列是否为空
|
// IsEmpty 判断队列是否为空
|
||||||
func (q *ArrayQueue) IsEmpty() bool {
|
func (q *ArrayQueue) IsEmpty() bool {
|
||||||
return q.rear-q.front == 0
|
return q.rear-q.front == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Offer 入队
|
// Offer 入队
|
||||||
func (q *ArrayQueue) Offer(v int) {
|
func (q *ArrayQueue) Offer(v int) {
|
||||||
// 当 rear == capacity 表示队列已满
|
// 当 rear == capacity 表示队列已满
|
||||||
@ -693,7 +752,6 @@ comments: true
|
|||||||
// 尾指针向后移动一位,越过尾部后返回到数组头部
|
// 尾指针向后移动一位,越过尾部后返回到数组头部
|
||||||
q.rear = (q.rear + 1) % q.capacity
|
q.rear = (q.rear + 1) % q.capacity
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poll 出队
|
// Poll 出队
|
||||||
func (q *ArrayQueue) Poll() any {
|
func (q *ArrayQueue) Poll() any {
|
||||||
if q.IsEmpty() {
|
if q.IsEmpty() {
|
||||||
@ -704,7 +762,6 @@ comments: true
|
|||||||
q.front = (q.front + 1) % q.capacity
|
q.front = (q.front + 1) % q.capacity
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peek 访问队首元素
|
// Peek 访问队首元素
|
||||||
func (q *ArrayQueue) Peek() any {
|
func (q *ArrayQueue) Peek() any {
|
||||||
if q.IsEmpty() {
|
if q.IsEmpty() {
|
||||||
@ -718,13 +775,100 @@ comments: true
|
|||||||
=== "JavaScript"
|
=== "JavaScript"
|
||||||
|
|
||||||
```js title="array_queue.js"
|
```js title="array_queue.js"
|
||||||
|
/* 基于环形数组实现的队列 */
|
||||||
|
class ArrayQueue {
|
||||||
|
#queue; // 用于存储队列元素的数组
|
||||||
|
#front = 0; // 头指针,指向队首
|
||||||
|
#rear = 0; // 尾指针,指向队尾 + 1
|
||||||
|
constructor(capacity) {
|
||||||
|
this.#queue = new Array(capacity);
|
||||||
|
}
|
||||||
|
/* 获取队列的容量 */
|
||||||
|
get capacity() {
|
||||||
|
return this.#queue.length;
|
||||||
|
}
|
||||||
|
/* 获取队列的长度 */
|
||||||
|
get size() {
|
||||||
|
// 由于将数组看作为环形,可能 rear < front ,因此需要取余数
|
||||||
|
return (this.capacity + this.#rear - this.#front) % this.capacity;
|
||||||
|
}
|
||||||
|
/* 判断队列是否为空 */
|
||||||
|
empty() {
|
||||||
|
return this.#rear - this.#front == 0;
|
||||||
|
}
|
||||||
|
/* 入队 */
|
||||||
|
offer(num) {
|
||||||
|
if (this.size == this.capacity)
|
||||||
|
throw new Error("队列已满");
|
||||||
|
// 尾结点后添加 num
|
||||||
|
this.#queue[this.#rear] = num;
|
||||||
|
// 尾指针向后移动一位,越过尾部后返回到数组头部
|
||||||
|
this.#rear = (this.#rear + 1) % this.capacity;
|
||||||
|
}
|
||||||
|
/* 出队 */
|
||||||
|
poll() {
|
||||||
|
const num = this.peek();
|
||||||
|
// 队头指针向后移动一位,若越过尾部则返回到数组头部
|
||||||
|
this.#front = (this.#front + 1) % this.capacity;
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
/* 访问队首元素 */
|
||||||
|
peek() {
|
||||||
|
if (this.empty())
|
||||||
|
throw new Error("队列为空");
|
||||||
|
return this.#queue[this.#front];
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "TypeScript"
|
=== "TypeScript"
|
||||||
|
|
||||||
```typescript title="array_queue.ts"
|
```typescript title="array_queue.ts"
|
||||||
|
/* 基于环形数组实现的队列 */
|
||||||
|
class ArrayQueue {
|
||||||
|
private queue: number[]; // 用于存储队列元素的数组
|
||||||
|
private front: number = 0; // 头指针,指向队首
|
||||||
|
private rear: number = 0; // 尾指针,指向队尾 + 1
|
||||||
|
private CAPACITY: number = 1e5;
|
||||||
|
constructor(capacity?: number) {
|
||||||
|
this.queue = new Array<number>(capacity ?? this.CAPACITY);
|
||||||
|
}
|
||||||
|
/* 获取队列的容量 */
|
||||||
|
get capacity(): number {
|
||||||
|
return this.queue.length;
|
||||||
|
}
|
||||||
|
/* 获取队列的长度 */
|
||||||
|
get size(): number {
|
||||||
|
// 由于将数组看作为环形,可能 rear < front ,因此需要取余数
|
||||||
|
return (this.capacity + this.rear - this.front) % this.capacity;
|
||||||
|
}
|
||||||
|
/* 判断队列是否为空 */
|
||||||
|
empty(): boolean {
|
||||||
|
return this.rear - this.front == 0;
|
||||||
|
}
|
||||||
|
/* 入队 */
|
||||||
|
offer(num: number): void {
|
||||||
|
if (this.size == this.capacity)
|
||||||
|
throw new Error("队列已满");
|
||||||
|
// 尾结点后添加 num
|
||||||
|
this.queue[this.rear] = num;
|
||||||
|
// 尾指针向后移动一位,越过尾部后返回到数组头部
|
||||||
|
this.rear = (this.rear + 1) % this.capacity;
|
||||||
|
}
|
||||||
|
/* 出队 */
|
||||||
|
poll(): number {
|
||||||
|
const num = this.peek();
|
||||||
|
// 队头指针向后移动一位,若越过尾部则返回到数组头部
|
||||||
|
this.front = (this.front + 1) % this.capacity;
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
/* 访问队首元素 */
|
||||||
|
peek(): number {
|
||||||
|
if (this.empty())
|
||||||
|
throw new Error("队列为空");
|
||||||
|
return this.queue[this.front];
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "C"
|
=== "C"
|
||||||
|
@ -350,19 +350,16 @@ comments: true
|
|||||||
// 使用内置包 list 来实现栈
|
// 使用内置包 list 来实现栈
|
||||||
data *list.List
|
data *list.List
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLinkedListStack 初始化链表
|
// NewLinkedListStack 初始化链表
|
||||||
func NewLinkedListStack() *LinkedListStack {
|
func NewLinkedListStack() *LinkedListStack {
|
||||||
return &LinkedListStack{
|
return &LinkedListStack{
|
||||||
data: list.New(),
|
data: list.New(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push 入栈
|
// Push 入栈
|
||||||
func (s *LinkedListStack) Push(value int) {
|
func (s *LinkedListStack) Push(value int) {
|
||||||
s.data.PushBack(value)
|
s.data.PushBack(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pop 出栈
|
// Pop 出栈
|
||||||
func (s *LinkedListStack) Pop() any {
|
func (s *LinkedListStack) Pop() any {
|
||||||
if s.IsEmpty() {
|
if s.IsEmpty() {
|
||||||
@ -372,7 +369,6 @@ comments: true
|
|||||||
s.data.Remove(e)
|
s.data.Remove(e)
|
||||||
return e.Value
|
return e.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peek 访问栈顶元素
|
// Peek 访问栈顶元素
|
||||||
func (s *LinkedListStack) Peek() any {
|
func (s *LinkedListStack) Peek() any {
|
||||||
if s.IsEmpty() {
|
if s.IsEmpty() {
|
||||||
@ -381,12 +377,10 @@ comments: true
|
|||||||
e := s.data.Back()
|
e := s.data.Back()
|
||||||
return e.Value
|
return e.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size 获取栈的长度
|
// Size 获取栈的长度
|
||||||
func (s *LinkedListStack) Size() int {
|
func (s *LinkedListStack) Size() int {
|
||||||
return s.data.Len()
|
return s.data.Len()
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsEmpty 判断栈是否为空
|
// IsEmpty 判断栈是否为空
|
||||||
func (s *LinkedListStack) IsEmpty() bool {
|
func (s *LinkedListStack) IsEmpty() bool {
|
||||||
return s.data.Len() == 0
|
return s.data.Len() == 0
|
||||||
@ -457,12 +451,6 @@ comments: true
|
|||||||
throw new EmptyStackException();
|
throw new EmptyStackException();
|
||||||
return stack.get(size() - 1);
|
return stack.get(size() - 1);
|
||||||
}
|
}
|
||||||
/* 访问索引 index 处元素 */
|
|
||||||
public int get(int index) {
|
|
||||||
if (index >= size())
|
|
||||||
throw new EmptyStackException();
|
|
||||||
return stack.get(index);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -499,12 +487,6 @@ comments: true
|
|||||||
throw out_of_range("栈为空");
|
throw out_of_range("栈为空");
|
||||||
return stack.back();
|
return stack.back();
|
||||||
}
|
}
|
||||||
/* 访问索引 index 处元素 */
|
|
||||||
int get(int index) {
|
|
||||||
if(index >= size())
|
|
||||||
throw out_of_range("索引越界");
|
|
||||||
return stack[index];
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -537,11 +519,6 @@ comments: true
|
|||||||
def peek(self):
|
def peek(self):
|
||||||
assert not self.is_empty(), "栈为空"
|
assert not self.is_empty(), "栈为空"
|
||||||
return self.__stack[-1]
|
return self.__stack[-1]
|
||||||
|
|
||||||
""" 访问索引 index 处元素 """
|
|
||||||
def get(self, index):
|
|
||||||
assert index < self.size(), "索引越界"
|
|
||||||
return self.__stack[index]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "Go"
|
=== "Go"
|
||||||
@ -613,19 +590,16 @@ comments: true
|
|||||||
}
|
}
|
||||||
/* 出栈 */
|
/* 出栈 */
|
||||||
pop() {
|
pop() {
|
||||||
if (this.empty()) throw "栈为空";
|
if (this.empty())
|
||||||
|
throw new Error("栈为空");
|
||||||
return this.stack.pop();
|
return this.stack.pop();
|
||||||
}
|
}
|
||||||
/* 访问栈顶元素 */
|
/* 访问栈顶元素 */
|
||||||
top() {
|
top() {
|
||||||
if (this.empty()) throw "栈为空";
|
if (this.empty())
|
||||||
|
throw new Error("栈为空");
|
||||||
return this.stack[this.stack.length - 1];
|
return this.stack[this.stack.length - 1];
|
||||||
}
|
}
|
||||||
/* 访问索引 index 处元素 */
|
|
||||||
get(index) {
|
|
||||||
if (index >= this.size) throw "索引越界";
|
|
||||||
return this.stack[index];
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -652,19 +626,16 @@ comments: true
|
|||||||
}
|
}
|
||||||
/* 出栈 */
|
/* 出栈 */
|
||||||
pop(): number | undefined {
|
pop(): number | undefined {
|
||||||
if (empty()) throw new Error('栈为空');
|
if (this.empty())
|
||||||
|
throw new Error('栈为空');
|
||||||
return this.stack.pop();
|
return this.stack.pop();
|
||||||
}
|
}
|
||||||
/* 访问栈顶元素 */
|
/* 访问栈顶元素 */
|
||||||
top(): number | undefined {
|
top(): number | undefined {
|
||||||
if (empty()) throw new Error('栈为空');
|
if (this.empty())
|
||||||
|
throw new Error('栈为空');
|
||||||
return this.stack[this.stack.length - 1];
|
return this.stack[this.stack.length - 1];
|
||||||
}
|
}
|
||||||
/* 访问索引 index 处元素 */
|
|
||||||
get(index: number): number | undefined {
|
|
||||||
if (index >= size()) throw new Error('索引越界');
|
|
||||||
return this.stack[index];
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -668,7 +668,7 @@ comments: true
|
|||||||
- **删除元素:** 与无序数组中的情况相同,使用 $O(n)$ 时间;
|
- **删除元素:** 与无序数组中的情况相同,使用 $O(n)$ 时间;
|
||||||
- **获取最小 / 最大元素:** 数组头部和尾部元素即是最小和最大元素,使用 $O(1)$ 时间;
|
- **获取最小 / 最大元素:** 数组头部和尾部元素即是最小和最大元素,使用 $O(1)$ 时间;
|
||||||
|
|
||||||
观察发现,无序数组和有序数组中的各类操作的时间复杂度是 “偏科” 的,即有的快有的慢;**而二叉搜索树的各项操作的时间复杂度都是对数阶,在数据量 $n$ 很大时有巨大优势**。
|
观察发现,无序数组和有序数组中的各项操作的时间复杂度是“偏科”的,即有的快有的慢;**而二叉搜索树的各项操作的时间复杂度都是对数阶,在数据量 $n$ 很大时有巨大优势**。
|
||||||
|
|
||||||
<div class="center-table" markdown>
|
<div class="center-table" markdown>
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
@ -129,27 +129,6 @@ comments: true
|
|||||||
|
|
||||||
值得注意,我们通常将「高度」和「深度」定义为“走过边的数量”,而有些题目或教材会将其定义为“走过结点的数量”,此时高度或深度都需要 + 1 。
|
值得注意,我们通常将「高度」和「深度」定义为“走过边的数量”,而有些题目或教材会将其定义为“走过结点的数量”,此时高度或深度都需要 + 1 。
|
||||||
|
|
||||||
## 二叉树最佳和最差结构
|
|
||||||
|
|
||||||
当二叉树的每层的结点都被填满时,达到「完美二叉树」;而当所有结点都偏向一边时,二叉树退化为「链表」。
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
<p align="center"> Fig. 二叉树的最佳和最差结构 </p>
|
|
||||||
|
|
||||||
如下表所示,在最佳和最差结构下,二叉树的叶结点数量、结点总数、高度等达到极大或极小值。
|
|
||||||
|
|
||||||
<div class="center-table" markdown>
|
|
||||||
|
|
||||||
| | 完美二叉树 | 链表 |
|
|
||||||
| ----------------------------- | ---------- | ---------- |
|
|
||||||
| 第 $i$ 层的结点数量 | $2^{i-1}$ | $1$ |
|
|
||||||
| 树的高度为 $h$ 时的叶结点数量 | $2^h$ | $1$ |
|
|
||||||
| 树的高度为 $h$ 时的结点总数 | $2^{h+1} - 1$ | $h + 1$ |
|
|
||||||
| 树的结点总数为 $n$ 时的高度 | $\log_2 (n+1) - 1$ | $n - 1$ |
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## 二叉树基本操作
|
## 二叉树基本操作
|
||||||
|
|
||||||
**初始化二叉树。** 与链表类似,先初始化结点,再构建引用指向(即指针)。
|
**初始化二叉树。** 与链表类似,先初始化结点,再构建引用指向(即指针)。
|
||||||
@ -343,361 +322,58 @@ comments: true
|
|||||||
|
|
||||||
插入结点会改变二叉树的原有逻辑结构,删除结点往往意味着删除了该结点的所有子树。因此,二叉树中的插入与删除一般都是由一套操作配合完成的,这样才能实现有意义的操作。
|
插入结点会改变二叉树的原有逻辑结构,删除结点往往意味着删除了该结点的所有子树。因此,二叉树中的插入与删除一般都是由一套操作配合完成的,这样才能实现有意义的操作。
|
||||||
|
|
||||||
## 二叉树遍历
|
## 常见二叉树类型
|
||||||
|
|
||||||
非线性数据结构的遍历操作比线性数据结构更加复杂,往往需要使用搜索算法来实现。常见的二叉树遍历方式有层序遍历、前序遍历、中序遍历、后序遍历。
|
### 完美二叉树
|
||||||
|
|
||||||
### 层序遍历
|
「完美二叉树 Perfect Binary Tree」的所有层的结点都被完全填满。在完美二叉树中,所有结点的度 = 2 ;若树高度 $= h$ ,则结点总数 $= 2^{h+1} - 1$ ,呈标准的指数级关系,反映着自然界中常见的细胞分裂。
|
||||||
|
|
||||||
「层序遍历 Hierarchical-Order Traversal」从顶至底、一层一层地遍历二叉树,并在每层中按照从左到右的顺序访问结点。
|
!!! tip
|
||||||
|
|
||||||
层序遍历本质上是「广度优先搜索 Breadth-First Traversal」,其体现着一种 “一圈一圈向外” 的层进遍历方式。
|
在中文社区中,完美二叉树常被称为「满二叉树」,请注意与完满二叉树区分。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
<p align="center"> Fig. 二叉树的层序遍历 </p>
|
### 完全二叉树
|
||||||
|
|
||||||
广度优先遍历一般借助「队列」来实现。队列的规则是 “先进先出” ,广度优先遍历的规则是 ”一层层平推“ ,两者背后的思想是一致的。
|
「完全二叉树 Complete Binary Tree」只有最底层的结点未被填满,且最底层结点尽量靠左填充。
|
||||||
|
|
||||||
=== "Java"
|
**完全二叉树非常适合用数组来表示**。如果按照层序遍历序列的顺序来存储,那么空结点 `null` 一定全部出现在序列的尾部,因此我们就可以不用存储这些 null 了。
|
||||||
|
|
||||||
```java title="binary_tree_bfs.java"
|

|
||||||
/* 层序遍历 */
|
|
||||||
List<Integer> hierOrder(TreeNode root) {
|
|
||||||
// 初始化队列,加入根结点
|
|
||||||
Queue<TreeNode> queue = new LinkedList<>() {{ add(root); }};
|
|
||||||
// 初始化一个列表,用于保存遍历序列
|
|
||||||
List<Integer> list = new ArrayList<>();
|
|
||||||
while (!queue.isEmpty()) {
|
|
||||||
TreeNode node = queue.poll(); // 队列出队
|
|
||||||
list.add(node.val); // 保存结点值
|
|
||||||
if (node.left != null)
|
|
||||||
queue.offer(node.left); // 左子结点入队
|
|
||||||
if (node.right != null)
|
|
||||||
queue.offer(node.right); // 右子结点入队
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "C++"
|
### 完满二叉树
|
||||||
|
|
||||||
```cpp title="binary_tree_bfs.cpp"
|
「完满二叉树 Full Binary Tree」除了叶结点之外,其余所有结点都有两个子结点。
|
||||||
/* 层序遍历 */
|
|
||||||
vector<int> hierOrder(TreeNode* root) {
|
|
||||||
// 初始化队列,加入根结点
|
|
||||||
queue<TreeNode*> queue;
|
|
||||||
queue.push(root);
|
|
||||||
// 初始化一个列表,用于保存遍历序列
|
|
||||||
vector<int> vec;
|
|
||||||
while (!queue.empty()) {
|
|
||||||
TreeNode* node = queue.front();
|
|
||||||
queue.pop(); // 队列出队
|
|
||||||
vec.push_back(node->val); // 保存结点
|
|
||||||
if (node->left != nullptr)
|
|
||||||
queue.push(node->left); // 左子结点入队
|
|
||||||
if (node->right != nullptr)
|
|
||||||
queue.push(node->right); // 右子结点入队
|
|
||||||
}
|
|
||||||
return vec;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "Python"
|

|
||||||
|
|
||||||
```python title="binary_tree_bfs.py"
|
### 平衡二叉树
|
||||||
|
|
||||||
```
|
「平衡二叉树 Balanced Binary Tree」中任意结点的左子树和右子树的高度之差的绝对值 $\leq 1$ 。
|
||||||
|
|
||||||
=== "Go"
|

|
||||||
|
|
||||||
```go title="binary_tree_bfs.go"
|
## 二叉树的退化
|
||||||
/* 层序遍历 */
|
|
||||||
func levelOrder(root *TreeNode) []int {
|
|
||||||
// 初始化队列,加入根结点
|
|
||||||
queue := list.New()
|
|
||||||
queue.PushBack(root)
|
|
||||||
// 初始化一个切片,用于保存遍历序列
|
|
||||||
nums := make([]int, 0)
|
|
||||||
for queue.Len() > 0 {
|
|
||||||
// poll
|
|
||||||
node := queue.Remove(queue.Front()).(*TreeNode)
|
|
||||||
// 保存结点
|
|
||||||
nums = append(nums, node.Val)
|
|
||||||
if node.Left != nil {
|
|
||||||
// 左子结点入队
|
|
||||||
queue.PushBack(node.Left)
|
|
||||||
}
|
|
||||||
if node.Right != nil {
|
|
||||||
// 右子结点入队
|
|
||||||
queue.PushBack(node.Right)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nums
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "JavaScript"
|
当二叉树的每层的结点都被填满时,达到「完美二叉树」;而当所有结点都偏向一边时,二叉树退化为「链表」。
|
||||||
|
|
||||||
```js title="binary_tree_bfs.js"
|
- 完美二叉树是一个二叉树的“最佳状态”,可以完全发挥出二叉树“分治”的优势;
|
||||||
/* 层序遍历 */
|
- 链表则是另一个极端,各项操作都变为线性操作,时间复杂度退化至 $O(n)$ ;
|
||||||
function hierOrder(root) {
|
|
||||||
// 初始化队列,加入根结点
|
|
||||||
let queue = [root];
|
|
||||||
// 初始化一个列表,用于保存遍历序列
|
|
||||||
let list = [];
|
|
||||||
while (queue.length) {
|
|
||||||
let node = queue.shift(); // 队列出队
|
|
||||||
list.push(node.val); // 保存结点
|
|
||||||
if (node.left)
|
|
||||||
queue.push(node.left); // 左子结点入队
|
|
||||||
if (node.right)
|
|
||||||
queue.push(node.right); // 右子结点入队
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "TypeScript"
|

|
||||||
|
|
||||||
```typescript title="binary_tree_bfs.ts"
|
<p align="center"> Fig. 二叉树的最佳和最差结构 </p>
|
||||||
/* 层序遍历 */
|
|
||||||
function hierOrder(root: TreeNode | null): number[] {
|
|
||||||
// 初始化队列,加入根结点
|
|
||||||
const queue = [root];
|
|
||||||
// 初始化一个列表,用于保存遍历序列
|
|
||||||
const list: number[] = [];
|
|
||||||
while (queue.length) {
|
|
||||||
let node = queue.shift() as TreeNode; // 队列出队
|
|
||||||
list.push(node.val); // 保存结点
|
|
||||||
if (node.left) {
|
|
||||||
queue.push(node.left); // 左子结点入队
|
|
||||||
}
|
|
||||||
if (node.right) {
|
|
||||||
queue.push(node.right); // 右子结点入队
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "C"
|
如下表所示,在最佳和最差结构下,二叉树的叶结点数量、结点总数、高度等达到极大或极小值。
|
||||||
|
|
||||||
```c title="binary_tree_bfs.c"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "C#"
|
|
||||||
|
|
||||||
```csharp title="binary_tree_bfs.cs"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### 前序、中序、后序遍历
|
|
||||||
|
|
||||||
相对地,前、中、后序遍历皆属于「深度优先遍历 Depth-First Traversal」,其体现着一种 “先走到尽头,再回头继续” 的回溯遍历方式。
|
|
||||||
|
|
||||||
如下图所示,左侧是深度优先遍历的的示意图,右上方是对应的递归实现代码。深度优先遍历就像是绕着整个二叉树的外围 “走” 一圈,走的过程中,在每个结点都会遇到三个位置,分别对应前序遍历、中序遍历、后序遍历。
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
<p align="center"> Fig. 二叉树的前 / 中 / 后序遍历 </p>
|
|
||||||
|
|
||||||
<div class="center-table" markdown>
|
<div class="center-table" markdown>
|
||||||
|
|
||||||
| 位置 | 含义 | 此处访问结点时对应 |
|
| | 完美二叉树 | 链表 |
|
||||||
| ---------- | ------------------------------------ | ----------------------------- |
|
| ----------------------------- | ---------- | ---------- |
|
||||||
| 橙色圆圈处 | 刚进入此结点,即将访问该结点的左子树 | 前序遍历 Pre-Order Traversal |
|
| 第 $i$ 层的结点数量 | $2^{i-1}$ | $1$ |
|
||||||
| 蓝色圆圈处 | 已访问完左子树,即将访问右子树 | 中序遍历 In-Order Traversal |
|
| 树的高度为 $h$ 时的叶结点数量 | $2^h$ | $1$ |
|
||||||
| 紫色圆圈处 | 已访问完左子树和右子树,即将返回 | 后序遍历 Post-Order Traversal |
|
| 树的高度为 $h$ 时的结点总数 | $2^{h+1} - 1$ | $h + 1$ |
|
||||||
|
| 树的结点总数为 $n$ 时的高度 | $\log_2 (n+1) - 1$ | $n - 1$ |
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
=== "Java"
|
|
||||||
|
|
||||||
```java title="binary_tree_dfs.java"
|
|
||||||
/* 前序遍历 */
|
|
||||||
void preOrder(TreeNode root) {
|
|
||||||
if (root == null) return;
|
|
||||||
// 访问优先级:根结点 -> 左子树 -> 右子树
|
|
||||||
list.add(root.val);
|
|
||||||
preOrder(root.left);
|
|
||||||
preOrder(root.right);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 中序遍历 */
|
|
||||||
void inOrder(TreeNode root) {
|
|
||||||
if (root == null) return;
|
|
||||||
// 访问优先级:左子树 -> 根结点 -> 右子树
|
|
||||||
inOrder(root.left);
|
|
||||||
list.add(root.val);
|
|
||||||
inOrder(root.right);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 后序遍历 */
|
|
||||||
void postOrder(TreeNode root) {
|
|
||||||
if (root == null) return;
|
|
||||||
// 访问优先级:左子树 -> 右子树 -> 根结点
|
|
||||||
postOrder(root.left);
|
|
||||||
postOrder(root.right);
|
|
||||||
list.add(root.val);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "C++"
|
|
||||||
|
|
||||||
```cpp title="binary_tree_dfs.cpp"
|
|
||||||
/* 前序遍历 */
|
|
||||||
void preOrder(TreeNode* root) {
|
|
||||||
if (root == nullptr) return;
|
|
||||||
// 访问优先级:根结点 -> 左子树 -> 右子树
|
|
||||||
vec.push_back(root->val);
|
|
||||||
preOrder(root->left);
|
|
||||||
preOrder(root->right);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 中序遍历 */
|
|
||||||
void inOrder(TreeNode* root) {
|
|
||||||
if (root == nullptr) return;
|
|
||||||
// 访问优先级:左子树 -> 根结点 -> 右子树
|
|
||||||
inOrder(root->left);
|
|
||||||
vec.push_back(root->val);
|
|
||||||
inOrder(root->right);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 后序遍历 */
|
|
||||||
void postOrder(TreeNode* root) {
|
|
||||||
if (root == nullptr) return;
|
|
||||||
// 访问优先级:左子树 -> 右子树 -> 根结点
|
|
||||||
postOrder(root->left);
|
|
||||||
postOrder(root->right);
|
|
||||||
vec.push_back(root->val);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "Python"
|
|
||||||
|
|
||||||
```python title="binary_tree_dfs.py"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "Go"
|
|
||||||
|
|
||||||
```go title="binary_tree_dfs.go"
|
|
||||||
/* 前序遍历 */
|
|
||||||
func preOrder(node *TreeNode) {
|
|
||||||
if node == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 访问优先级:根结点 -> 左子树 -> 右子树
|
|
||||||
nums = append(nums, node.Val)
|
|
||||||
preOrder(node.Left)
|
|
||||||
preOrder(node.Right)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 中序遍历 */
|
|
||||||
func inOrder(node *TreeNode) {
|
|
||||||
if node == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 访问优先级:左子树 -> 根结点 -> 右子树
|
|
||||||
inOrder(node.Left)
|
|
||||||
nums = append(nums, node.Val)
|
|
||||||
inOrder(node.Right)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 后序遍历 */
|
|
||||||
func postOrder(node *TreeNode) {
|
|
||||||
if node == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 访问优先级:左子树 -> 右子树 -> 根结点
|
|
||||||
postOrder(node.Left)
|
|
||||||
postOrder(node.Right)
|
|
||||||
nums = append(nums, node.Val)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "JavaScript"
|
|
||||||
|
|
||||||
```js title="binary_tree_dfs.js"
|
|
||||||
/* 前序遍历 */
|
|
||||||
function preOrder(root){
|
|
||||||
if (root === null) return;
|
|
||||||
// 访问优先级:根结点 -> 左子树 -> 右子树
|
|
||||||
list.push(root.val);
|
|
||||||
preOrder(root.left);
|
|
||||||
preOrder(root.right);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 中序遍历 */
|
|
||||||
function inOrder(root) {
|
|
||||||
if (root === null) return;
|
|
||||||
// 访问优先级:左子树 -> 根结点 -> 右子树
|
|
||||||
inOrder(root.left);
|
|
||||||
list.push(root.val);
|
|
||||||
inOrder(root.right);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 后序遍历 */
|
|
||||||
function postOrder(root) {
|
|
||||||
if (root === null) return;
|
|
||||||
// 访问优先级:左子树 -> 右子树 -> 根结点
|
|
||||||
postOrder(root.left);
|
|
||||||
postOrder(root.right);
|
|
||||||
list.push(root.val);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "TypeScript"
|
|
||||||
|
|
||||||
```typescript title="binary_tree_dfs.ts"
|
|
||||||
/* 前序遍历 */
|
|
||||||
function preOrder(root: TreeNode | null): void {
|
|
||||||
if (root === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 访问优先级:根结点 -> 左子树 -> 右子树
|
|
||||||
list.push(root.val);
|
|
||||||
preOrder(root.left);
|
|
||||||
preOrder(root.right);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 中序遍历 */
|
|
||||||
function inOrder(root: TreeNode | null): void {
|
|
||||||
if (root === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 访问优先级:左子树 -> 根结点 -> 右子树
|
|
||||||
inOrder(root.left);
|
|
||||||
list.push(root.val);
|
|
||||||
inOrder(root.right);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 后序遍历 */
|
|
||||||
function postOrder(root: TreeNode | null): void {
|
|
||||||
if (root === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 访问优先级:左子树 -> 右子树 -> 根结点
|
|
||||||
postOrder(root.left);
|
|
||||||
postOrder(root.right);
|
|
||||||
list.push(root.val);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "C"
|
|
||||||
|
|
||||||
```c title="binary_tree_dfs.c"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "C#"
|
|
||||||
|
|
||||||
```csharp title="binary_tree_dfs.cs"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! note
|
|
||||||
|
|
||||||
使用循环一样可以实现前、中、后序遍历,但代码相对繁琐,有兴趣的同学可以自行实现。
|
|
||||||
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
362
docs/chapter_tree/binary_tree_traversal.md
Normal file
@ -0,0 +1,362 @@
|
|||||||
|
---
|
||||||
|
comments: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# 二叉树遍历
|
||||||
|
|
||||||
|
非线性数据结构的遍历操作比线性数据结构更加复杂,往往需要使用搜索算法来实现。常见的二叉树遍历方式有层序遍历、前序遍历、中序遍历、后序遍历。
|
||||||
|
|
||||||
|
## 层序遍历
|
||||||
|
|
||||||
|
「层序遍历 Hierarchical-Order Traversal」从顶至底、一层一层地遍历二叉树,并在每层中按照从左到右的顺序访问结点。
|
||||||
|
|
||||||
|
层序遍历本质上是「广度优先搜索 Breadth-First Traversal」,其体现着一种“一圈一圈向外”的层进遍历方式。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<p align="center"> Fig. 二叉树的层序遍历 </p>
|
||||||
|
|
||||||
|
广度优先遍历一般借助「队列」来实现。队列的规则是“先进先出”,广度优先遍历的规则是 ”一层层平推“ ,两者背后的思想是一致的。
|
||||||
|
|
||||||
|
=== "Java"
|
||||||
|
|
||||||
|
```java title="binary_tree_bfs.java"
|
||||||
|
/* 层序遍历 */
|
||||||
|
List<Integer> hierOrder(TreeNode root) {
|
||||||
|
// 初始化队列,加入根结点
|
||||||
|
Queue<TreeNode> queue = new LinkedList<>() {{ add(root); }};
|
||||||
|
// 初始化一个列表,用于保存遍历序列
|
||||||
|
List<Integer> list = new ArrayList<>();
|
||||||
|
while (!queue.isEmpty()) {
|
||||||
|
TreeNode node = queue.poll(); // 队列出队
|
||||||
|
list.add(node.val); // 保存结点值
|
||||||
|
if (node.left != null)
|
||||||
|
queue.offer(node.left); // 左子结点入队
|
||||||
|
if (node.right != null)
|
||||||
|
queue.offer(node.right); // 右子结点入队
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "C++"
|
||||||
|
|
||||||
|
```cpp title="binary_tree_bfs.cpp"
|
||||||
|
/* 层序遍历 */
|
||||||
|
vector<int> hierOrder(TreeNode* root) {
|
||||||
|
// 初始化队列,加入根结点
|
||||||
|
queue<TreeNode*> queue;
|
||||||
|
queue.push(root);
|
||||||
|
// 初始化一个列表,用于保存遍历序列
|
||||||
|
vector<int> vec;
|
||||||
|
while (!queue.empty()) {
|
||||||
|
TreeNode* node = queue.front();
|
||||||
|
queue.pop(); // 队列出队
|
||||||
|
vec.push_back(node->val); // 保存结点
|
||||||
|
if (node->left != nullptr)
|
||||||
|
queue.push(node->left); // 左子结点入队
|
||||||
|
if (node->right != nullptr)
|
||||||
|
queue.push(node->right); // 右子结点入队
|
||||||
|
}
|
||||||
|
return vec;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Python"
|
||||||
|
|
||||||
|
```python title="binary_tree_bfs.py"
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Go"
|
||||||
|
|
||||||
|
```go title="binary_tree_bfs.go"
|
||||||
|
/* 层序遍历 */
|
||||||
|
func levelOrder(root *TreeNode) []int {
|
||||||
|
// 初始化队列,加入根结点
|
||||||
|
queue := list.New()
|
||||||
|
queue.PushBack(root)
|
||||||
|
// 初始化一个切片,用于保存遍历序列
|
||||||
|
nums := make([]int, 0)
|
||||||
|
for queue.Len() > 0 {
|
||||||
|
// poll
|
||||||
|
node := queue.Remove(queue.Front()).(*TreeNode)
|
||||||
|
// 保存结点
|
||||||
|
nums = append(nums, node.Val)
|
||||||
|
if node.Left != nil {
|
||||||
|
// 左子结点入队
|
||||||
|
queue.PushBack(node.Left)
|
||||||
|
}
|
||||||
|
if node.Right != nil {
|
||||||
|
// 右子结点入队
|
||||||
|
queue.PushBack(node.Right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nums
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "JavaScript"
|
||||||
|
|
||||||
|
```js title="binary_tree_bfs.js"
|
||||||
|
/* 层序遍历 */
|
||||||
|
function hierOrder(root) {
|
||||||
|
// 初始化队列,加入根结点
|
||||||
|
let queue = [root];
|
||||||
|
// 初始化一个列表,用于保存遍历序列
|
||||||
|
let list = [];
|
||||||
|
while (queue.length) {
|
||||||
|
let node = queue.shift(); // 队列出队
|
||||||
|
list.push(node.val); // 保存结点
|
||||||
|
if (node.left)
|
||||||
|
queue.push(node.left); // 左子结点入队
|
||||||
|
if (node.right)
|
||||||
|
queue.push(node.right); // 右子结点入队
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "TypeScript"
|
||||||
|
|
||||||
|
```typescript title="binary_tree_bfs.ts"
|
||||||
|
/* 层序遍历 */
|
||||||
|
function hierOrder(root: TreeNode | null): number[] {
|
||||||
|
// 初始化队列,加入根结点
|
||||||
|
const queue = [root];
|
||||||
|
// 初始化一个列表,用于保存遍历序列
|
||||||
|
const list: number[] = [];
|
||||||
|
while (queue.length) {
|
||||||
|
let node = queue.shift() as TreeNode; // 队列出队
|
||||||
|
list.push(node.val); // 保存结点
|
||||||
|
if (node.left) {
|
||||||
|
queue.push(node.left); // 左子结点入队
|
||||||
|
}
|
||||||
|
if (node.right) {
|
||||||
|
queue.push(node.right); // 右子结点入队
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "C"
|
||||||
|
|
||||||
|
```c title="binary_tree_bfs.c"
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "C#"
|
||||||
|
|
||||||
|
```csharp title="binary_tree_bfs.cs"
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## 前序、中序、后序遍历
|
||||||
|
|
||||||
|
相对地,前、中、后序遍历皆属于「深度优先遍历 Depth-First Traversal」,其体现着一种“先走到尽头,再回头继续”的回溯遍历方式。
|
||||||
|
|
||||||
|
如下图所示,左侧是深度优先遍历的的示意图,右上方是对应的递归实现代码。深度优先遍历就像是绕着整个二叉树的外围“走”一圈,走的过程中,在每个结点都会遇到三个位置,分别对应前序遍历、中序遍历、后序遍历。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<p align="center"> Fig. 二叉树的前 / 中 / 后序遍历 </p>
|
||||||
|
|
||||||
|
<div class="center-table" markdown>
|
||||||
|
|
||||||
|
| 位置 | 含义 | 此处访问结点时对应 |
|
||||||
|
| ---------- | ------------------------------------ | ----------------------------- |
|
||||||
|
| 橙色圆圈处 | 刚进入此结点,即将访问该结点的左子树 | 前序遍历 Pre-Order Traversal |
|
||||||
|
| 蓝色圆圈处 | 已访问完左子树,即将访问右子树 | 中序遍历 In-Order Traversal |
|
||||||
|
| 紫色圆圈处 | 已访问完左子树和右子树,即将返回 | 后序遍历 Post-Order Traversal |
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
=== "Java"
|
||||||
|
|
||||||
|
```java title="binary_tree_dfs.java"
|
||||||
|
/* 前序遍历 */
|
||||||
|
void preOrder(TreeNode root) {
|
||||||
|
if (root == null) return;
|
||||||
|
// 访问优先级:根结点 -> 左子树 -> 右子树
|
||||||
|
list.add(root.val);
|
||||||
|
preOrder(root.left);
|
||||||
|
preOrder(root.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 中序遍历 */
|
||||||
|
void inOrder(TreeNode root) {
|
||||||
|
if (root == null) return;
|
||||||
|
// 访问优先级:左子树 -> 根结点 -> 右子树
|
||||||
|
inOrder(root.left);
|
||||||
|
list.add(root.val);
|
||||||
|
inOrder(root.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 后序遍历 */
|
||||||
|
void postOrder(TreeNode root) {
|
||||||
|
if (root == null) return;
|
||||||
|
// 访问优先级:左子树 -> 右子树 -> 根结点
|
||||||
|
postOrder(root.left);
|
||||||
|
postOrder(root.right);
|
||||||
|
list.add(root.val);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "C++"
|
||||||
|
|
||||||
|
```cpp title="binary_tree_dfs.cpp"
|
||||||
|
/* 前序遍历 */
|
||||||
|
void preOrder(TreeNode* root) {
|
||||||
|
if (root == nullptr) return;
|
||||||
|
// 访问优先级:根结点 -> 左子树 -> 右子树
|
||||||
|
vec.push_back(root->val);
|
||||||
|
preOrder(root->left);
|
||||||
|
preOrder(root->right);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 中序遍历 */
|
||||||
|
void inOrder(TreeNode* root) {
|
||||||
|
if (root == nullptr) return;
|
||||||
|
// 访问优先级:左子树 -> 根结点 -> 右子树
|
||||||
|
inOrder(root->left);
|
||||||
|
vec.push_back(root->val);
|
||||||
|
inOrder(root->right);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 后序遍历 */
|
||||||
|
void postOrder(TreeNode* root) {
|
||||||
|
if (root == nullptr) return;
|
||||||
|
// 访问优先级:左子树 -> 右子树 -> 根结点
|
||||||
|
postOrder(root->left);
|
||||||
|
postOrder(root->right);
|
||||||
|
vec.push_back(root->val);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Python"
|
||||||
|
|
||||||
|
```python title="binary_tree_dfs.py"
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Go"
|
||||||
|
|
||||||
|
```go title="binary_tree_dfs.go"
|
||||||
|
/* 前序遍历 */
|
||||||
|
func preOrder(node *TreeNode) {
|
||||||
|
if node == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 访问优先级:根结点 -> 左子树 -> 右子树
|
||||||
|
nums = append(nums, node.Val)
|
||||||
|
preOrder(node.Left)
|
||||||
|
preOrder(node.Right)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 中序遍历 */
|
||||||
|
func inOrder(node *TreeNode) {
|
||||||
|
if node == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 访问优先级:左子树 -> 根结点 -> 右子树
|
||||||
|
inOrder(node.Left)
|
||||||
|
nums = append(nums, node.Val)
|
||||||
|
inOrder(node.Right)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 后序遍历 */
|
||||||
|
func postOrder(node *TreeNode) {
|
||||||
|
if node == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 访问优先级:左子树 -> 右子树 -> 根结点
|
||||||
|
postOrder(node.Left)
|
||||||
|
postOrder(node.Right)
|
||||||
|
nums = append(nums, node.Val)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "JavaScript"
|
||||||
|
|
||||||
|
```js title="binary_tree_dfs.js"
|
||||||
|
/* 前序遍历 */
|
||||||
|
function preOrder(root){
|
||||||
|
if (root === null) return;
|
||||||
|
// 访问优先级:根结点 -> 左子树 -> 右子树
|
||||||
|
list.push(root.val);
|
||||||
|
preOrder(root.left);
|
||||||
|
preOrder(root.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 中序遍历 */
|
||||||
|
function inOrder(root) {
|
||||||
|
if (root === null) return;
|
||||||
|
// 访问优先级:左子树 -> 根结点 -> 右子树
|
||||||
|
inOrder(root.left);
|
||||||
|
list.push(root.val);
|
||||||
|
inOrder(root.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 后序遍历 */
|
||||||
|
function postOrder(root) {
|
||||||
|
if (root === null) return;
|
||||||
|
// 访问优先级:左子树 -> 右子树 -> 根结点
|
||||||
|
postOrder(root.left);
|
||||||
|
postOrder(root.right);
|
||||||
|
list.push(root.val);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "TypeScript"
|
||||||
|
|
||||||
|
```typescript title="binary_tree_dfs.ts"
|
||||||
|
/* 前序遍历 */
|
||||||
|
function preOrder(root: TreeNode | null): void {
|
||||||
|
if (root === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 访问优先级:根结点 -> 左子树 -> 右子树
|
||||||
|
list.push(root.val);
|
||||||
|
preOrder(root.left);
|
||||||
|
preOrder(root.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 中序遍历 */
|
||||||
|
function inOrder(root: TreeNode | null): void {
|
||||||
|
if (root === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 访问优先级:左子树 -> 根结点 -> 右子树
|
||||||
|
inOrder(root.left);
|
||||||
|
list.push(root.val);
|
||||||
|
inOrder(root.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 后序遍历 */
|
||||||
|
function postOrder(root: TreeNode | null): void {
|
||||||
|
if (root === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 访问优先级:左子树 -> 右子树 -> 根结点
|
||||||
|
postOrder(root.left);
|
||||||
|
postOrder(root.right);
|
||||||
|
list.push(root.val);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "C"
|
||||||
|
|
||||||
|
```c title="binary_tree_dfs.c"
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "C#"
|
||||||
|
|
||||||
|
```csharp title="binary_tree_dfs.cs"
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
使用循环一样可以实现前、中、后序遍历,但代码相对繁琐,有兴趣的同学可以自行实现。
|
@ -1,44 +0,0 @@
|
|||||||
---
|
|
||||||
comments: true
|
|
||||||
---
|
|
||||||
|
|
||||||
# 常见二叉树类型
|
|
||||||
|
|
||||||
## 完美二叉树
|
|
||||||
|
|
||||||
「完美二叉树 Perfect Binary Tree」,其所有层的结点都被完全填满。
|
|
||||||
|
|
||||||
!!! tip
|
|
||||||
|
|
||||||
在中文社区中,完美二叉树常被称为「满二叉树」,请注意与完满二叉树区分。
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
完美二叉树的性质有:
|
|
||||||
|
|
||||||
- 若树高度 $= h$ ,则结点总数 $= 2^h - 1$;
|
|
||||||
- (TODO)
|
|
||||||
|
|
||||||
## 完全二叉树
|
|
||||||
|
|
||||||
「完全二叉树 Complete Binary Tree」只有最底层的结点未被填满,且最底层结点都尽量靠左填充。
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
完全二叉树有一个很好的性质,可以用「数组」来表示。
|
|
||||||
|
|
||||||
- (TODO)
|
|
||||||
|
|
||||||
## 完满二叉树
|
|
||||||
|
|
||||||
「完满二叉树 Full Binary Tree」除了叶结点之外,其余所有结点都有两个子结点。
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 平衡二叉树
|
|
||||||
|
|
||||||
**「平衡二叉树 Balanced Binary Tree」** ,其任意结点的左子树和右子树的高度之差的绝对值 $\leq 1$ 。
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
- (TODO)
|
|
@ -157,7 +157,7 @@ nav:
|
|||||||
- 小结: chapter_hashing/summary.md
|
- 小结: chapter_hashing/summary.md
|
||||||
- 二叉树:
|
- 二叉树:
|
||||||
- 二叉树(Binary Tree): chapter_tree/binary_tree.md
|
- 二叉树(Binary Tree): chapter_tree/binary_tree.md
|
||||||
- 二叉树常见类型: chapter_tree/binary_tree_types.md
|
- 二叉树遍历: chapter_tree/binary_tree_traversal.md
|
||||||
- 二叉搜索树: chapter_tree/binary_search_tree.md
|
- 二叉搜索树: chapter_tree/binary_search_tree.md
|
||||||
- AVL 树 *: chapter_tree/avl_tree.md
|
- AVL 树 *: chapter_tree/avl_tree.md
|
||||||
- 小结: chapter_tree/summary.md
|
- 小结: chapter_tree/summary.md
|
||||||
|