C++数据结构与算法总结

C++数据结构与算法

学习算法参考:https://www.hello-algo.com/

Visual Studio快捷键:https://learn.microsoft.com/zh-cn/visualstudio/ide/default-keyboard-shortcuts-in-visual-studio?view=vs-2019

启动时不调试	Ctrl+F5
设置文档格式	Ctrl+K、Ctrl+D
注释选定内容	Ctrl+K、Ctrl+C
取消注释选定内容	Ctrl+K、Ctrl+U 
行 - 删除	Ctrl+DShift+L
复制行 Ctrl+D

变量和常量命名规范

  • 类名:首字母大写和驼峰原则
  • 方法名(函数)、类成员变量、局部变量、package包命名:首字母小写和驼峰原则
  • 常量:大写字母和下划线

vector

#include
using namespace std;

int main() {
	//初始化
	vectorvec = { 1,2 };

	//构建一个5×5,且所有元素均为0的二维动态数组
	vector> arrVec(5, vector(5, 0));

	/* 访问元素 */
	int num = vec[1];  // 访问索引 1 处的元素

	/* 更新元素 */
	vec[1] = 0;  // 将索引 1 处的元素更新为 0

	/*清空列表*/
	vec.clear();


	/* 尾部添加元素 */
	vec.push_back(1);
	vec.push_back(3);
	//加到尾部
	vec.emplace_back(4);

	//插入元素,插入头部
	vec.insert(vec.begin(), 11);

	/* 中间插入元素 */
	vec.insert(vec.begin() + 3, 6);  // 在索引 3 处插入数字 6

	/* 删除元素 */
	vec.erase(vec.begin() + 3);      // 删除索引 3 处的元素

	/* 拼接两个列表 */
	vector vec2 = { 6, 8, 7, 10, 9 };
	// 将vec 后面添加 vec2 
	vec.insert(vec.end(), vec2.begin(), vec2.end());

	/* 排序列表 */
	sort(vec.begin(), vec.end());  // 排序后,默认元素从小到大排列

	//从大到小排序
	sort(vec.begin(), vec.end(), [](int a, int b) {
		return a > b;
	});

	//遍历
	for (int v : vec) cout << v << " ";

	return 0;

}

链表

单链表

#include
using namespace std;

/***
*
*单链表
*
***/

//链表数据结构
struct ListNode {
	int val;
	ListNode* next;
	ListNode(int x) : val(x), next(nullptr) {}; //构造方法
};


//遍历链表
void printListNode(ListNode* node) {
	ListNode* root = node;
	while (root != nullptr) {
		cout << root->val << " ";
		root = root->next;
	}
	cout << endl;
}

//增加节点
void addListNode(ListNode* node, ListNode* newNode) {
	cout << "add num = " << newNode->val << endl;
	newNode->next = node->next;
	node->next = newNode;
}

//删除节点
void deleListNode(ListNode* node) {
	//只有一个元素的时候
	if (node->next == nullptr) return;
	ListNode* delnode = node->next;
	cout << "dele num = " << delnode->val << endl;
	node->next = delnode->next;
	delete delnode; //删除这个节点的内存,在 C 和 C++ 等语言中,我们需要手动释放节点内存。
}

//修改节点
void updateListNode(ListNode* node, int updateNum) {
	//只有一个元素的时候
	if (node->next == nullptr) return;
	ListNode* updateNode = node->next;
	cout << "original num = " << updateNode->val << " to update num = " << updateNum << endl;
	updateNode->val = updateNum;

}

//访问某一节点
void lookListNode(ListNode* node, int index) {
	ListNode* root = node;
	int idx = index;
	while (root != nullptr && idx > 0) {
		root = root->next;
		--idx;
	}
	int num = root->val;
	printListNode(node);
	cout << "index = " << index << ",val = " << num << endl;
}

//查找某个值所在的节点和索引值
void findListNode(ListNode* node, int target) {
	ListNode* root = node;
	int idx = 0;
	while (root != nullptr) {
		if (root->val == target) {
			cout << "idx = " << idx << ",target = " << target << ",after the node is: ";
			printListNode(root);
			break;
		}
		root = root->next;
		++idx;
	}
}

int main() {
	/* 初始化链表 2 -> 5 -> 3 -> 8 */
	ListNode* n0 = new ListNode(2);
	ListNode* n1 = new ListNode(5);
	ListNode* n2 = new ListNode(3);
	ListNode* n3 = new ListNode(8);
	n0->next = n1;
	n1->next = n2;
	n2->next = n3;

	cout << "[+] original node:" << endl;
	printListNode(n0);
	cout << "[+] add node:" << endl;
	ListNode* newNode = new ListNode(7);
	addListNode(n0, newNode);
	printListNode(n0);
	cout << "[+] dele node:" << endl;
	deleListNode(n0);
	printListNode(n0);
	cout << "[+] update node with num:" << endl;
	updateListNode(n0, 9);
	printListNode(n0);
	cout << "[+] look node with index:" << endl;
	lookListNode(n0, 2);
	cout << "[+] find node with target:" << endl;
	printListNode(n0);
	findListNode(n0, 9);
	return 0;
}

Stack栈

「栈 stack」是一种遵循先进后出的逻辑的线性数据结构。栈的底层由链表实现。

#include
using namespace std;

int main() {
	/* 初始化栈 */
	stack stack;

	/* 元素入栈 */
	stack.push(1);
	stack.push(3);
	stack.push(2);
	stack.push(6);

	/* 访问栈顶元素 */
	int top = stack.top();
	cout << "top num = " << top << endl;

	/* 元素出栈 */
	stack.pop(); // 无返回值,把栈顶的值删除

	/* 获取栈的长度 */
	int size = stack.size();
	cout << "size = " << size << endl;

	/* 判断是否为空 */
	bool empty = stack.empty(); //非空返回false
	cout << "empty = " << empty << endl;

	return 0;
}

基于链表实现的栈

#include
using namespace std;

/* 基于链表实现的栈 */
class LinkedListStack {

private:
	//链表数据结构
	struct ListNode {
		int val;
		ListNode* next;
		ListNode(int x) : val(x), next(nullptr) {}; //构造方法
	};

	ListNode* stackTop; // 将头节点作为栈顶
	int stkSize;        // 栈的长度

public:

	// LinkedListStack类的构造方法
	LinkedListStack() {
		cout << "启动了 LinkedListStack的构造方法" << endl;
		stackTop = nullptr;
		stkSize = 0;
	}


	/**
	析构函数(destructor) 与构造函数相反,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统自动执行析构函数。
	析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,delete会自动调用析构函数后释放内存)。
	**/
	~LinkedListStack() {
		// 遍历链表删除节点,释放内存
		freeMemoryLinkedList(stackTop);
	}

	void freeMemoryLinkedList(ListNode* stackTop) {
		cout << "调用了 LinkedListStack 析构函数" << endl;
		delete stackTop;
	};


	/* 获取栈的长度 */
	int size() {
		return stkSize;
	}

	/* 判断栈是否为空 */
	bool isEmpty() {
		return size() == 0;
	}

	/* 入栈 */
	void push(int num) {
		ListNode* node = new ListNode(num);
        //新的值查到头部(栈顶)
		node->next = stackTop;
        //stackTop变成头节点
		stackTop = node;
		stkSize++;
	}

	/* 出栈 */
	void pop() {
		int num = top();
		ListNode* tmp = stackTop;
		stackTop = stackTop->next;
		// 释放内存
		delete tmp;
		stkSize--;
	}

	/* 访问栈顶元素 */
	int top() {
		if (isEmpty())
			throw out_of_range("Stack is null");
		return stackTop->val;
	}

	/* 将 List 转化为 Array 并返回 */
	vector toVector() {
		ListNode* node = stackTop;
		vector res(size());
		for (int i = res.size() - 1; i >= 0; i--) {
			res[i] = node->val;
			node = node->next;
		}
		return res;
	}

};

int main() {
	LinkedListStack listStack;
	// 栈顶到栈底顺序: 8 ->5 ->2 ->3 ->1
	listStack.push(1);
	listStack.push(3);
	listStack.push(2);
	listStack.push(5);
	listStack.push(8);

	vector arr = listStack.toVector();
	for (int v : arr) cout << v << " ";
	cout << endl;

	listStack.pop();
	cout << "after pop:" << endl;

	vector arr2 = listStack.toVector();
	for (int v2 : arr2) cout << v2 << " ";
	cout << endl;

	int topNum = listStack.top();
	cout << "topNum = " << topNum << " Stack size = " << listStack.size() << endl;

	return 0;
}

Queue队列

「队列 queue」是一种遵循先进先出规则的线性数据结构。队列的底层由链表实现。

#include
using namespace std;

int main() {

	/* 初始化队列 */
	queue queue;

	/* 元素入队 */
	queue.push(1);
	queue.push(3);
	queue.push(2);
	queue.push(5);
	queue.push(4);

	/* 访问队首元素 */
	int front = queue.front();
	/* 访问队尾元素 */
	int back = queue.back();

	cout << "front = " << front << " back = " << back << endl;

	/* 元素出队,会删除元素 */
	queue.pop();
	
	/* 获取队列的长度 */
	int size = queue.size();

	/* 判断队列是否为空 */
	bool empty = queue.empty(); //非空返回false
	cout << "size = " << size << " empty = " << empty << endl;

	return 0;
}

基于链表实现的队列

链表的“头节点”和“尾节点”分别视为“队首”和“队尾”,规定队尾仅可添加节点,队首仅可删除节点。

#include
using namespace std;

/* 基于链表实现的队列 */
class LinkedListQueue {
private:
	//链表数据结构
	struct ListNode {
		int val;
		ListNode* next;
		ListNode(int x) : val(x), next(nullptr) {}; //构造方法
	};

	ListNode* front, * rear; // 头节点 front ,尾节点 rear
	int queSize;

public:
	LinkedListQueue() {
		cout << "启动了 LinkedListQueue的构造方法" << endl;
		front = nullptr;
		rear = nullptr;
		queSize = 0;
	}

	~LinkedListQueue() {
		// 遍历链表删除节点,释放内存
		freeMemoryLinkedList(front);
	}

	void freeMemoryLinkedList(ListNode* front) {
		cout << "调用了 LinkedListQueue 析构函数" << endl;
		delete front;
	}

	/* 获取队列的长度 */
	int size() {
		return queSize;
	}

	/* 判断队列是否为空 */
	bool isEmpty() {
		return queSize == 0;
	}

	/* 尾部节点入队 */
	void push(int num) {
		// 尾节点后添加 num
		ListNode* node = new ListNode(num);
		// 如果队列为空,则令头、尾节点都指向该节点
		if (front == nullptr) {
			front = node;
			rear = node;
		}
		// 如果队列不为空,则将该节点添加到尾节点后
		else {
			rear->next = node;
			rear = node;
		}
		queSize++;
	}

	/* 头部节点出队 */
	void pop() {
		// 删除头节点
		ListNode* tmp = front;
		front = front->next;
		// 释放内存
		delete tmp;
		queSize--;
	}

	/* 访问队首元素 */
	int peek() {
		if (size() == 0)
			throw out_of_range("Queue is null");
		return front->val;
	}

	/* 将链表转化为 Vector 并返回 */
	vector toVector() {
		ListNode* node = front;
		vector res(size());
		for (int i = 0; i < res.size(); i++) {
			res[i] = node->val;
			node = node->next;
		}
		return res;
	}
};


int main() {
	LinkedListQueue queue;
	queue.push(1);
	queue.push(3);
	queue.push(2);
	queue.push(4);
	queue.push(8);
	queue.push(6);

	vector arr = queue.toVector();
	for (int v : arr) cout << v << " ";
	cout << endl;

	queue.pop();
	cout << "after pop:" << endl;

	vector arr2 = queue.toVector();
	for (int v2 : arr2) cout << v2 << " ";
	cout << endl;

	int topNum = queue.peek();
	cout << "peekNum = " << topNum << " Stack size = " << queue.size() << endl;

	return 0;
}

double-ended queue双向队列

在头部和尾部都允许执行元素的添加或删除操作。

pushFirst()	将元素添加至队首	
pushLast()	将元素添加至队尾	
popFirst()	删除队首元素	
popLast()	删除队尾元素	
peekFirst()	访问队首元素	
peekLast()	访问队尾元素
#include
using namespace std;

int main() {
	dequedeque;
	//1->2->3->4
	deque.push_back(3); // 添加至队尾
	deque.push_back(4);
	deque.push_front(2);// 添加至队首
	deque.push_front(1);

	/* 访问元素,不删除元素 */
	int front = deque.front(); // 队首元素
	int back = deque.back();   // 队尾元素
	cout << "front = " << front << " back = " << back << endl;

	/* 元素出队 ,删除元素*/
	deque.pop_front();  // 队首元素出队
	deque.pop_back();   // 队尾元素出队

	/* 获取双向队列的长度 */
	int size = deque.size();

	/* 判断双向队列是否为空 */
	bool empty = deque.empty();
	cout << "size = " << size << " empty = " << empty << endl;
	return 0;
}

基于双向链表实现双向队列

双向队列底层是由双向链表实现

#include
using namespace std;


/* 基于双向链表实现的双向队列 */
class LinkedListDeque {
private:
	/* 双向链表节点 */
	struct DoublyListNode {
		int val;              // 节点值
		DoublyListNode* next; // 后继节点指针
		DoublyListNode* prev; // 前驱节点指针
		DoublyListNode(int val) : val(val), prev(nullptr), next(nullptr) {
		}
	};

	DoublyListNode* front, * rear; // 头节点 front ,尾节点 rear
	int queSize = 0;              // 双向队列的长度

public:
	/* 构造方法 */
	LinkedListDeque() : front(nullptr), rear(nullptr) {
		cout << "调用了 LinkedListDeque 的构造方法" << endl;
	}

	/* 析构方法 */
	~LinkedListDeque() {
		cout << "调用了 LinkedListDeque 的析构方法" << endl;
		// 遍历链表删除全部节点,释放内存,
		DoublyListNode* pre, * cur = front;
		while (cur != nullptr) {
			pre = cur;
			cur = cur->next;
			delete pre;
		}
	}

	/* 获取双向队列的长度 */
	int size() {
		return queSize;
	}

	/* 判断双向队列是否为空 */
	bool isEmpty() {
		return size() == 0;
	}

	/* 入队操作 */
	void push(int num, bool isFront) {
		DoublyListNode* node = new DoublyListNode(num);
		// 若链表为空,则令 front, rear 都指向 node
		if (isEmpty())
			front = rear = node;
		// 队首入队操作
		else if (isFront) {
			// 将 node 添加至链表头部 (新增的node  <->  当前的front)
			front->prev = node;
			node->next = front;
			front = node; // 更新头节点
			// 队尾入队操作
		}
		else {
			// 将 node 添加至链表尾部 (当前的rear <-> 新增的node )
			rear->next = node;
			node->prev = rear;
			rear = node; // 更新尾节点
		}
		queSize++; // 更新队列长度
	}

	/* 队首入队 */
	void pushFirst(int num) {
		push(num, true);
	}

	/* 队尾入队 */
	void pushLast(int num) {
		push(num, false);
	}

	/* 出队操作 */
	int pop(bool isFront) {
		if (isEmpty())
			throw out_of_range("Double queue is null!");
		int val;
		// 队首出队操作
		if (isFront) {
			val = front->val; // 暂存头节点值
			// 删除头节点
			DoublyListNode* fNext = front->next;  //(第一个的头节点front  <->  下一个节点fNext)
			if (fNext != nullptr) {
				fNext->prev = nullptr;
				front->next = nullptr;
				delete front;  //在 C 和 C++ 中需要手动释放内存。
			}
			front = fNext; // 更新头节点
			// 队尾出队操作
		}
		else {
			val = rear->val; // 暂存尾节点值
			// 删除尾节点
			DoublyListNode* rPrev = rear->prev;    //(上一个节点rPrev  <->  最后的尾节点rear)
			if (rPrev != nullptr) {
				rPrev->next = nullptr;
				rear->prev = nullptr;
				delete rear;  //在 C 和 C++ 中需要手动释放内存。
			}
			rear = rPrev; // 更新尾节点
		}
		queSize--; // 更新队列长度
		return val;
	}

	/* 队首出队 */
	int popFirst() {
		return pop(true);
	}

	/* 队尾出队 */
	int popLast() {
		return pop(false);
	}

	/* 访问队首元素 */
	int peekFirst() {
		if (isEmpty())
			throw out_of_range("Double queue is null!");
		return front->val;
	}

	/* 访问队尾元素 */
	int peekLast() {
		if (isEmpty())
			throw out_of_range("Double queue is null!");
		return rear->val;
	}

	/* 返回数组用于打印 */
	vector toVector() {
		DoublyListNode* node = front;
		vector res(size());
		for (int i = 0; i < res.size(); i++) {
			res[i] = node->val;
			node = node->next;
		}
		return res;
	}
};
int main() {
	LinkedListDeque doubleQueue;
    // 0->1->2->3->4->5
	doubleQueue.pushFirst(2);  //队列头部添加元素
	doubleQueue.pushFirst(1);
	doubleQueue.pushFirst(0);
	doubleQueue.pushLast(3); //队列尾部添加元素
	doubleQueue.pushLast(4);
	doubleQueue.pushLast(5);

	vectordqueue = doubleQueue.toVector();
	cout << "dqueue num:" << endl;
	for (int dq : dqueue) cout << dq << " ";
	cout << endl;

	doubleQueue.popFirst();//队列头部删除弹出元素
	doubleQueue.popLast();//队列尾部删除弹出元素

	cout << "after popFirst and  popLast:" << endl;
	int peekFirstNum = doubleQueue.peekFirst();  //访问头部元素,不删除
	int peekLastNum = doubleQueue.peekLast();  //访问尾部元素,不删除
	cout << "peekFirstNum = " << peekFirstNum << " peekLastNum = " << peekLastNum << endl;

	int size = doubleQueue.size();

	bool empty = doubleQueue.isEmpty(); //非空返回false
	cout << "size = " << size << " empty = " << empty << endl;
	return 0;
}

unordered_map哈希表

哈希表中进行增删查改的时间复杂度都是O(1)

#include
using namespace std;


int main() {
	/* 初始化哈希表 */
	unordered_map map;

	/* 添加操作 */
	// 在哈希表中添加键值对 (key, value)
	map[1] = "小哈";
	map[2] = "小啰";
	map[3] = "小算";
	map[4] = "小法";
	map[5] = "小鸭";

	/* 查询操作 */
	// 向哈希表输入键 key ,得到值 value
	string name = map[1];

	/* 删除操作 */
	// 在哈希表中删除键值对 (key, value),参数是key
	map.erase(2);

	//查找元素,参数是key,如果key不存在,find会返回end
	if (map.find(3) != map.end()) cout << "find isExistKey true " << endl;
	else cout << "find isExistKey false " << endl;

	//查找元素,参数是key,存在返回1,不存在返回0
	int isExistKey = map.count(5);
	cout << "count isExistKey = " << isExistKey << " (存在返回1,不存在返回0)" << endl;

	/* 遍历哈希表 */
// 遍历键值对 key->value
	for (auto kv : map) {
		cout << kv.first << " -> " << kv.second << endl;
	}

	//c++ 17特性
	/*for (auto [k, v] : map) {
		cout << "key = " << k << "  value = " << v << endl;
	}*/

	//删除全部元素
	map.clear();

	//统计map的大小
	int size = map.size();
	cout << "after map clear,the map size = " << size << endl;
	return 0;
}

哈希表的实现

输入一个 key ,哈希函数的计算过程分为以下两步。

    1. 通过某种哈希算法 hash() 计算得到哈希值。
    1. 将哈希值对桶数量(数组长度)capacity 取模,从而获取该 key 对应的数组索引 index
index = hash(key) % capacity

就可以利用 index 在哈希表中访问对应的桶,从而获取 value

#include
using namespace std;


/* 键值对 */
struct Pair {
public:
	int key;
	string val;
	Pair(int key, string val) {
		this->key = key;
		this->val = val;
	}
};

/* 基于数组简易实现的哈希表 */
class ArrayHashMap {
private:
	// [{key,val},{key,val},{key,val} ...]
	vector buckets;  //定义数组桶
	int capacity = 100; // 数组桶大小

public:
	ArrayHashMap() {
		cout << "调用了 ArrayHashMap 的构造方法" << endl;
		// 初始化数组,包含 100 个桶
		buckets = vector(capacity);
	}

	~ArrayHashMap() {
		cout << "调用了 ArrayHashMap 的析构方法" << endl;
		// 释放内存
		for (const auto& bucket : buckets) {
			delete bucket;
		}
		buckets.clear();
	}

	/* 哈希函数 */
	int hashFunc(int key) {
		//对key值取模,如 4 % 100 = 4
		int index = key % capacity;
		return index;
	}

	/* 查询操作 */
	string get(int key) {
		int index = hashFunc(key);
		Pair* pair = buckets[index];
		if (pair == nullptr)
			return nullptr;
		//key通过hash后的index,拿到index对应的pair
		return pair->val;
	}

	/* 添加操作 */
	void put(int key, string val) {
		//初始化pair对象
		Pair* pair = new Pair(key, val);
		int index = hashFunc(key);
		//key通过hash后的index添加到桶里
		buckets[index] = pair;
	}

	/* 删除操作 */
	void remove(int key) {
		int index = hashFunc(key);
		// 先释放内存,在置为 nullptr
		delete buckets[index];
		buckets[index] = nullptr;
	}

	/* 获取所有键值对 */
	vector pairSet() {
		vector pairSet;
		for (Pair* pair : buckets) {
			if (pair != nullptr) {
				pairSet.push_back(pair);
			}
		}
		return pairSet;
	}

	/* 获取所有键 */
	vector keySet() {
		vector keySet;
		for (Pair* pair : buckets) {
			if (pair != nullptr) {
				keySet.push_back(pair->key);
			}
		}
		return keySet;
	}

	/* 获取所有值 */
	vector valueSet() {
		vector valueSet;
		for (Pair* pair : buckets) {
			if (pair != nullptr) {
				valueSet.push_back(pair->val);
			}
		}
		return valueSet;
	}

	/* 打印哈希表 */
	void print() {
		for (Pair* kv : pairSet()) {
			cout << kv->key << " -> " << kv->val << endl;
		}
	}
};


int main() {
	ArrayHashMap map;
	map.put(0, "0");
	map.put(1, "1");
	map.put(2, "2");
	map.put(3, "3");
	map.put(4, "4");

	int getKey = 2;
	string val = map.get(getKey);
	cout << "getKey = " << getKey << " value = " << val << endl;

	int removeKey = 4;
	map.remove(removeKey);

	// 或者 map.print()
    vector vps = map.pairSet();
    for (Pair* vp : vps) {
        cout << "key = " << vp->key << " value = " << vp->val << endl;
    }

	return 0;
}

哈希冲突

通常情况下哈希函数的输入空间远大于输出空间,改良哈希表数据结构,使得哈希表可以在存在哈希冲突时正常工作。即当哈希冲突比较严重时,才执行扩容操作。哈希表的结构改良方法主要包括“链式地址”和“开放寻址”。

链式地址

在原始哈希表中,每个桶仅能存储一个键值对。「链式地址 separate chaining」将单个元素转换为链表,将键值对作为链表节点,将所有发生冲突的键值对都存储在同一链表中。

  • 查询元素:输入 key ,经过哈希函数得到桶索引,即可访问链表头节点,然后遍历链表并对比 key 以查找目标键值对。
  • 添加元素:先通过哈希函数访问链表头节点,然后将节点(即键值对)添加到链表中。
  • 删除元素:根据哈希函数的结果访问链表头部,接着遍历链表以查找目标节点,并将其删除。
#include
using namespace std;


/* 键值对 */
struct Pair {
	int key;
	string val;
	Pair(int key, string val) {
		this->key = key;
		this->val = val;
	}
};

/* 链式地址哈希表 */
class HashMapChaining {
private:
	int size;                       // 所有键值对的总和个数
	int capacity;                   // 最外层的哈希表容量
	double loadThres;               // 触发扩容的负载因子阈值,这里设置成2/3
	int extendRatio;                // 扩容倍数。以下实现包含哈希表扩容方法。当负载因子超过2/3时,我们将哈希表扩容至2倍。
	//[[{key,val},{key,val},{key,val}...],[{key,val},{key,val},{key,val}...],[{key,val},{key,val},{key,val}...]]
	vector> buckets; // 桶数组

public:

	/* 构造方法 ,且初始化变量的值*/
	HashMapChaining() : size(0), capacity(4), loadThres(2.0 / 3.0), extendRatio(2) {
		cout << "调用了 HashMapChaining 的构造方法" << endl;
		//重新定义桶数组buckets的大小
		buckets.resize(capacity);
	}

	/* 析构方法 */
	~HashMapChaining() {
		cout << "调用了 HashMapChaining 的析构方法" << endl;
		for (auto& bucket : buckets) {
			for (Pair* pair : bucket) {
				// 释放内存
				delete pair;
			}
		}
	}

	/* 哈希函数 */
	int hashFunc(int key) {
		// key % 数组桶大小的容量
		//index是buckets第一层数组的数据
		int index = key % capacity;
		return index;
	}

	/* 负载因子 */
	double loadFactor() {
		//所有键值对的大小容量跟最外层的桶容量的占比
		return (double)size / (double)capacity;
	}

	/* 扩容哈希表(最外层) */
	void extend() {
		// 暂存原哈希表
		vector> bucketsTmp = buckets;
		// 初始化扩容后的新哈希表,扩容至原来的2倍
		capacity *= extendRatio;
		//清空原来的桶数组的数据
		buckets.clear();
		//重新定义桶数组的大小
		buckets.resize(capacity);
		size = 0;
		// 将键值对从原哈希表搬运至新哈希表
		for (auto& bucket : bucketsTmp) {
			for (Pair* pair : bucket) {
				//重新存数据
				put(pair->key, pair->val);
				// 删除释放临时的内存
				delete pair;
			}
		}
	}

	/* 添加操作 */
	void put(int key, string val) {
		// 当存储数据时,发现负载因子超过2/3阈值时,执行扩容
		//也就是说所有键值对的大小容量跟最外层的桶容量的占比大于2/3时则扩容
		if (loadFactor() > loadThres) {
			extend();
		}
		int index = hashFunc(key);
		// 遍历桶,若遇到指定 key存在时 ,则更新对应 val 并返回
		for (Pair* pair : buckets[index]) {
			if (pair->key == key) {
				pair->val = val;
				cout << "find the key data,key = " << key << ",update it!" << endl;
				return;
			}
		}
		// 若无该 key ,则将键值对添加至尾部
		buckets[index].push_back(new Pair(key, val));
		size++;
	}

	/* 查询操作 */
	string get(int key) {
		int index = hashFunc(key);
		// 遍历桶,若找到 key 则返回对应 val
		for (Pair* pair : buckets[index]) {
			if (pair->key == key) {
				return pair->val;
			}
		}
		cout << "Not find the key = " << key << "data!" << endl;
		// 若未找到 key 则返回 nullptr
		return nullptr;
	}

	/* 删除操作 */
	void remove(int key) {
		int index = hashFunc(key);
		vector& bucket = buckets[index];
		// 遍历桶,从中删除键值对
		for (int i = 0; i < bucket.size(); i++) {
			if (bucket[i]->key == key) {
				Pair* tmp = bucket[i];
				bucket.erase(bucket.begin() + i); // 从中删除键值对
				delete tmp;                       // 释放内存
				size--;
				return;
			}
		}
	}

	//返回整个外层桶数组的大小,也就是说是capacity的大小
	int mapSize() {
		return capacity;
	}

	//返回相同key的桶数组大小
	int sizeWithKey(int key) {
		int index = hashFunc(key);
		vectorbucket = buckets[index];
		return bucket.size();
	}

	/* 打印哈希表 */
	void print() {
		for (vector& bucket : buckets) {
			cout << "[";
			for (Pair* pair : bucket) {
				cout << pair->key << " -> " << pair->val << ", ";
			}
			cout << "]\n";
		}
	}
};


int main() {

	HashMapChaining mapChain;
	mapChain.put(0, "0");
	mapChain.put(1, "1");

	int Key1 = 1;
	string val = mapChain.get(Key1);
	cout << "Key1 = " << Key1 << ",val = " << val << endl;

	mapChain.print();

	int capacity = mapChain.mapSize();
	cout << "capacity = " << capacity << endl;

	mapChain.put(5, "5");
	mapChain.put(3, "3");
	mapChain.put(4, "4");

	mapChain.print();

	int capacity2 = mapChain.mapSize();
	cout << "after put data ,the capacity2 = " << capacity2 << endl;

	return 0;
}

开放寻址

「开放寻址 open addressing」不引入额外的数据结构,而是通过“多次探测”来处理哈希冲突,探测方式主要包括线性探测、平方探测、多次哈希等。

不能在开放寻址哈希表中直接删除元素。这是因为删除元素会在数组内产生一个空桶 None ,而当查询元素时,线性探测到该空桶就会返回,因此在该空桶之下的元素都无法再被访问到,程序可能误判这些元素不存在。

为了解决该问题,我们可以采用「懒删除 lazy deletion」机制:它不直接从哈希表中移除元素,而是利用一个常量 TOMBSTONE 来标记这个桶。在该机制下,None 和 TOMBSTONE 都代表空桶,都可以放置键值对。但不同的是,线性探测到 TOMBSTONE 时应该继续遍历,因为其之下可能还存在键值对。

然而,懒删除可能会加速哈希表的性能退化。这是因为每次删除操作都会产生一个删除标记,随着 TOMBSTONE 的增加,搜索时间也会增加,因为线性探测可能需要跳过多个 TOMBSTONE 才能找到目标元素。

为此,考虑在线性探测中记录遇到的首个 TOMBSTONE 的索引,并将搜索到的目标元素与该 TOMBSTONE 交换位置。这样做的好处是当每次查询或添加元素时,元素会被移动至距离理想位置(探测起始点)更近的桶,从而优化查询效率。

#include
using namespace std;

/* 键值对 */
struct Pair {
	int key;
	string val;
	Pair(int key, string val) {
		this->key = key;
		this->val = val;
	}
};


/* 开放寻址哈希表 */
class HashMapOpenAddressing {
private:
	int size;                             // 键值对数量
	int capacity = 4;                     // 哈希表容量
	const double loadThres = 2.0 / 3.0;     // 触发扩容的负载因子阈值
	const int extendRatio = 2;            // 扩容倍数
	vector buckets;               // 桶数组
	Pair* TOMBSTONE = new Pair(-1, "-1"); // 删除标记

public:
	/* 构造方法 */
	HashMapOpenAddressing() : size(0), buckets(capacity, nullptr) {
		cout << "调用了 HashMapOpenAddressing 的构造方法" << endl;
	}

	/* 析构方法 */
	~HashMapOpenAddressing() {
		cout << "调用了 HashMapOpenAddressing 的析构方法" << endl;
		for (Pair* pair : buckets) {
			if (pair != nullptr && pair != TOMBSTONE) {
				delete pair;
			}
		}
		delete TOMBSTONE;
	}

	/* 哈希函数 */
	int hashFunc(int key) {
		return key % capacity;
	}

	/* 负载因子 */
	double loadFactor() {
		return (double)size / capacity;
	}

	/* 搜索 key 对应的桶索引 */
	int findBucket(int key) {
		int index = hashFunc(key);
		//首次标记
		int firstTombstone = -1;
		// 线性探测,当遇到空桶时跳出
		while (buckets[index] != nullptr) {
			// 若遇到 key ,返回对应桶索引
			if (buckets[index]->key == key) {
				// 若之前标记过的,不是第一次删除标记的,则将index对应的键值对移动到firstTombstone索引这里
				if (firstTombstone != -1) {
					//也就是说现在找到的pair键值对移动到上次删除的键值对桶里。
					buckets[firstTombstone] = buckets[index];
					//现在的pair键值对就标记为删除了。
					buckets[index] = TOMBSTONE;
					return firstTombstone; // 因为移动了键值对,就返回移动的那个桶索引
				}
				return index; // 还没删除过桶索引,默认返回桶索引就行了。
			}
			//没找到key,且是首个删除标记和当前index索引被标记了TOMBSTONE
			if (firstTombstone == -1 && buckets[index] == TOMBSTONE) {
				//首次的删除标记被更新了
				firstTombstone = index;
			}
			// 计算桶索引,越过尾部返回头部
			index = (index + 1) % capacity;
		}
		// 若 key 不存在,且还没找到删除标记的桶索引,则返回index索引,否则返回删除标记的索引
		return firstTombstone == -1 ? index : firstTombstone;
	}

	/* 查询操作 */
	string get(int key) {
		// 搜索 key 对应的桶索引
		int index = findBucket(key);
		// 若找到键值对,且不时TOMBSTONE标记的,则返回对应 val
		if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {
			return buckets[index]->val;
		}
		// 若键值对不存在,则返回空字符串
		return "";
	}

	/* 添加操作 */
	void put(int key, string val) {
		// 当负载因子超过阈值时,执行扩容
		if (loadFactor() > loadThres) {
			extend();
		}
		// 搜索 key 对应的桶索引
		int index = findBucket(key);
		// 若找到键值对,且不是TOMBSTONE标记的,则覆盖 val 并返回
		if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {
			buckets[index]->val = val;
			return;
		}
		// 若键值对不存在,则添加该键值对
		buckets[index] = new Pair(key, val);
		size++;
	}

	/* 删除操作 */
	void remove(int key) {
		// 搜索 key 对应的桶索引
		int index = findBucket(key);
		// 若找到键值对,删除它,然后标记TOMBSTONE
		if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {
			delete buckets[index];
			buckets[index] = TOMBSTONE;
			size--;
		}
	}

	int sizeMap() {
		return capacity;
	}

	/* 扩容哈希表 */
	void extend() {
		// 暂存原哈希表
		vector bucketsTmp = buckets;
		// 初始化扩容后的新哈希表
		capacity *= extendRatio;
		buckets = vector(capacity, nullptr);
		size = 0;
		// 将键值对从原哈希表搬运至新哈希表
		for (Pair* pair : bucketsTmp) {
			if (pair != nullptr && pair != TOMBSTONE) {
				put(pair->key, pair->val);
				delete pair;
			}
		}
	}

	/* 打印哈希表 */
	void print() {
		for (Pair* pair : buckets) {
			if (pair == nullptr) {
				cout << "nullptr" << endl;
			}
			else if (pair == TOMBSTONE) {
				cout << "TOMBSTONE" << endl;
			}
			else {
				cout << pair->key << " -> " << pair->val << endl;
			}
		}
	}
};

int main() {
	HashMapOpenAddressing openAddrMap;
	openAddrMap.put(0, "0");
	openAddrMap.put(1, "1");
	openAddrMap.put(2, "2");
	openAddrMap.put(3, "3");
	openAddrMap.put(4, "4");

	string val = openAddrMap.get(2);
	cout << "val = " << val << endl;

	openAddrMap.remove(3);

	openAddrMap.print();

	int capacitySize = openAddrMap.sizeMap();
	cout << "capacitySize = " << capacitySize << endl;

	return 0;
}

哈希算法设计

  • 加法哈希:对输入的每个字符的 ASCII 码进行相加,将得到的总和作为哈希值。
  • 乘法哈希:利用了乘法的不相关性,每轮乘以一个常数,将各个字符的 ASCII 码累积到哈希值中。
  • 异或哈希:将输入数据的每个元素通过异或操作累积到一个哈希值中。
  • 旋转哈希:将每个字符的 ASCII 码累积到一个哈希值中,每次累积之前都会对哈希值进行旋转操作。

每种哈希算法的最后一步都是对大质数 1000000007 取模,以确保哈希值在合适的范围内。

当我们使用大质数作为模数时,可以最大化地保证哈希值的均匀分布。因为质数不会与其他数字存在公约数,可以减少因取模操作而产生的周期性模式,从而避免哈希冲突。

总而言之,通常选取质数作为模数,并且这个质数最好足够大,以尽可能消除周期性模式,提升哈希算法的稳健性。

#include
using namespace std;

/* 加法哈希 */
int addHash(string key) {
	long long hash = 0;
	const int MODULUS = 1000000007;
	for (unsigned char c : key) {
		hash = (hash + (int)c) % MODULUS;
	}
	return (int)hash;
}

/* 乘法哈希 */
int mulHash(string key) {
	long long hash = 0;
	const int MODULUS = 1000000007;
	for (unsigned char c : key) {
		hash = (31 * hash + (int)c) % MODULUS;
	}
	return (int)hash;
}

/* 异或哈希 */
int xorHash(string key) {
	int hash = 0;
	const int MODULUS = 1000000007;
	for (unsigned char c : key) {
		hash ^= (int)c;
	}
	return hash & MODULUS;
}

/* 旋转哈希 */
int rotHash(string key) {
	long long hash = 0;
	const int MODULUS = 1000000007;
	for (unsigned char c : key) {
		hash = ((hash << 4) ^ (hash >> 28) ^ (int)c) % MODULUS;
	}
	return (int)hash;
}


int main() {
	string str = "360357923174893";
	int addNum = addHash(str);
	cout << "addNum :" << addNum << endl;
	int mulNum = mulHash(str);
	cout << "mulNum :" << mulNum << endl;
	int xorNum= xorHash(str);
	cout << "xorNum :" << xorNum << endl;
	int rotNum = rotHash(str);
	cout << "rotNum :" << rotNum << endl;
	return 0;
}

二叉树

「二叉树 binary tree」是一种非线性数据结构,二叉树的基本单元是节点,每个节点包含:值、左子节点引用、右子节点引用。

每个节点都有两个引用(指针),分别指向「左子节点 left-child node」(节点是偶数)和「右子节点 right-child node」(节点是奇数),该节点被称为这两个子节点的「父节点 parent node」。当给定一个二叉树的节点时,我们将该节点的左子节点及其以下节点形成的树称为该节点的「左子树 left subtree」,同理可得「右子树 right subtree」。

在二叉树中,除叶节点外,其他所有节点都包含子节点和非空子树

  • 「根节点 root node」:位于二叉树顶层的节点,没有父节点。
  • 「叶节点 leaf node」:没有子节点的节点,其两个指针均指向 None 。
  • 「边 edge」:连接两个节点的线段,即节点引用(指针)。
  • 节点所在的「层 level」:从顶至底递增,根节点所在层为 1 。
  • 节点的「度 degree」:节点的子节点的数量。在二叉树中,度的取值范围是 0、1、2 。
  • 二叉树的「高度 height」:从根节点到最远叶节点所经过的边的数量。
  • 节点的「深度 depth」:从根节点到该节点所经过的边的数量。
  • 节点的「高度 height」:从最远叶节点到该节点所经过的边的数量。

完美二叉树

完美二叉树又称满二叉树,「完美二叉树 perfect binary tree」所有层的节点都被完全填满。在完美二叉树中,叶节点的度为 0 ,其余所有节点的度都为 2 ;若树高度为 ℎ ,则节点总数为 2^(ℎ+1)−1 ,呈现标准的指数级关系,反映了自然界中常见的细胞分裂现象。

若节点的索引为i ,则该节点的左子节点索引为 2i+1 ,右子节点索引为 2i+2

满二叉树中第n层的节点个数为
2 ( n − 1 ) 2^{(n-1)} 2(n1)
满二叉树中全部节点的个数为:
2 n − 1 2^n-1 2n1

完全二叉树

「完全二叉树 complete binary tree」只有最底层的节点未被填满,且最底层节点尽量靠左填充。

完满二叉树

「完满二叉树 full binary tree」除了叶节点之外,其余所有节点都有两个子节点。

平衡二叉树

「平衡二叉树 balanced binary tree」中任意节点的左子树和右子树的高度之差的绝对值不超过 1 。

左子树高度 - 右子树高度 = d ,|d| <= 1

搜索二叉树

「二叉搜索树 binary search tree」满足以下条件。

    1. 对于根节点,左子树中所有节点的值 < 根节点的值 < 右子树中所有节点的值
    1. 任意节点的左、右子树也是二叉搜索树,即同样满足条件 1.

查找节点

给定目标节点值 num ,可以根据二叉搜索树的性质来查找。声明一个节点 cur ,从二叉树的根节点 root 出发,循环比较节点值 cur.valnum 之间的大小关系。

  • cur.val < num ,说明目标节点在 cur 的右子树中,因此执行 cur = cur.right
  • cur.val > num ,说明目标节点在 cur 的左子树中,因此执行 cur = cur.left
  • cur.val = num ,说明找到目标节点,跳出循环并返回该节点。

插入节点

  1. 查找插入位置:与查找操作相似,从根节点出发,根据当前节点值和 num 的大小关系循环向下搜索,直到越过叶节点(遍历至 None )时跳出循环。
  2. 在该位置插入节点:初始化节点 num ,将该节点置于 None 的位置。

二叉搜索树不允许存在重复节点,否则将违反其定义。因此,若待插入节点在树中已存在,则不执行插入,直接返回。

为了实现插入节点,我们需要借助节点 pre 保存上一轮循环的节点。这样在遍历至 None 时,我们可以获取到其父节点,从而完成节点插入操作。

删除节点

先在二叉树中查找到目标节点,再将其从二叉树中删除。

与插入节点类似,我们需要保证在删除操作完成后,二叉搜索树的“左子树 < 根节点 < 右子树”的性质仍然满足。

当待删除节点的度为 0 时,表示该节点是叶节点,可以直接删除。

当待删除节点的度为 1 时,将待删除节点替换为其子节点即可。

当待删除节点的度为 2 时,我们无法直接删除它,而需要使用一个节点替换该节点。由于要保持二叉搜索树“左 < 根 < 右”的性质,因此这个节点可以是右子树的最小节点或左子树的最大节点

假设我们选择右子树的最小节点(即中序遍历的下一个节点),则删除操作流程如下。

  1. cur找到待删除节点在“中序遍历序列”中的下一个节点,记为 tmp
  2. tmp 的值覆盖待删除节点的值,并在树中递归删除节点 tmp

二叉搜索树中进行中序遍历时,总是会优先遍历下一个最小节点,从而得出一个重要性质:二叉搜索树的中序遍历序列是升序的

#include
using namespace std;

// 搜索二叉树

/* 二叉树节点结构体 */
struct TreeNode {
	int val;
	TreeNode* left;
	TreeNode* right;
	TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

//BFS遍历
void BFS(TreeNode* root) {
	//初始化队列
	queue queue;
	queue.push(root);
	//保存遍历序列
	vectorvec;
	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);
	}
	cout << "BFS: ";
	for (int v : vec) cout << v << " ";
	cout << endl;
}

//搜索二叉树,参数num为要寻找的节点值
TreeNode* searchTreeNode(TreeNode* root, int num) {
	TreeNode* cur = root;
	//左节点的值 < 根节点的值 < 右节点的值
	while (cur != nullptr) {
		if (cur->val < num) cur = cur->right;  //往右节点找
		else if (cur->val > num) cur = cur->left; //往左节点找
		else break; //找到了,跳出循环
	}
	return cur;
}

//搜索二叉树 插入节点 参数num为要插入的节点值
void addSearchTreeNode(TreeNode* root, int num) {
	//若树为空,则复制上该节点值
	if (root == nullptr) {
		root = new TreeNode(num);
		return;
	}

	TreeNode* cur = root; // 当前节点
	TreeNode* pre = nullptr; // 上一个节点
	while (cur != nullptr) {
		//存在该值,直接返回
		if (cur->val == num) return;

		//当前的节点不断的赋值给上一个节点,更新上次节点
		pre = cur;
		//左节点的值 < 根节点的值 < 右节点的值
		if (cur->val < num) cur = cur->right;//往右节点找
		else cur = cur->left;//往左节点找
	}
	//找到了pre最后的位置,插入该值
	TreeNode* newNode = new TreeNode(num);
	if (pre->val < num)  pre->right = newNode; //插到pre节点的右边
	else pre->left = newNode;//插到pre节点的左边
}

//搜索二叉树 删除节点 参数num为要删除的节点值
void deleSearchTreeNode(TreeNode* root, int num) {
	if (root == nullptr) return;
	TreeNode* cur = root;
	TreeNode* pre = nullptr;
	while (cur != nullptr) {
		//找到相同的值,跳出循环
		if (cur->val == num) break;
		pre = cur;//更新上次节点
		if (cur->val < num) cur = cur->right;
		else cur = cur->left;
	}
	// 若无待删除节点,则直接返回
	if (cur == nullptr) return;
	//cur节点的度为0或1时
	if (cur->left == nullptr || cur->right == nullptr) {
		//child 为cur 的左节点或者cur的右节点
		TreeNode* child = cur->left != nullptr ? cur->left : cur->right;
		//删除cur节点
		if (cur != root) {
			if (pre->left == cur) pre->left = child;
			else pre->right = child;
		}
		else root = child; //若删除节点为根节点,则重新指定根节点
		delete cur;
	}
	else {
		//cur节点的度为2时

		//通过中序遍历拿到cur的下一个节点,右子树最小节点,接就是说遍历cur的左子树
		TreeNode* tmpNode = cur->right;
		while (tmpNode->left != nullptr) tmpNode = tmpNode->left;
		int deleVal = tmpNode->val;

		//递归删除tmpNode节点
		deleSearchTreeNode(cur, tmpNode->val);

		//用tmpNode的值覆盖cur的值
		cur->val = deleVal;
	}
}

int main() {
	/* 初始化二叉树 */
	TreeNode* n8 = new TreeNode(8);
	TreeNode* n4 = new TreeNode(4);
	TreeNode* n12 = new TreeNode(12);
	TreeNode* n3 = new TreeNode(3);
	TreeNode* n6 = new TreeNode(6);
	TreeNode* n10 = new TreeNode(10);
	TreeNode* n14 = new TreeNode(14);
	TreeNode* n5 = new TreeNode(5);
	TreeNode* n7 = new TreeNode(7);
	TreeNode* n9 = new TreeNode(9);
	TreeNode* n11 = new TreeNode(11);
	TreeNode* n13 = new TreeNode(13);
	TreeNode* n15 = new TreeNode(15);
	// 构建引用指向(即指针)
	n8->left = n4;
	n8->right = n12;

	n4->left = n3;
	n4->right = n6;

	n12->left = n10;
	n12->right = n14;

	n6->left = n5;
	n6->right = n7;

	n10->left = n9;
	n10->right = n11;

	n14->left = n13;
	n14->right = n15;

	BFS(n8);

	//删除节点4
	deleSearchTreeNode(n8, 4);
	BFS(n8);

	return 0;
}

搜索二叉树的查找元素、插入元素、删除元素的时间复制复杂度均为O(logn)

平衡二叉搜素树AVL

确保在持续添加和删除节点后,AVL 树不会退化,从而使得各种操作的时间复杂度保持在 O(log⁡n) 级别。换句话说,在需要频繁进行增删查改操作的场景中,AVL 树能始终保持高效的数据操作性能,具有很好的应用价值。

AVL 树既是二叉搜索树也是平衡二叉树,同时满足这两类二叉树的所有性质,因此也被称为「平衡二叉搜索树 balanced binary search tree」。

“节点高度”是指从该节点到最远叶节点的距离,即所经过的“边”的数量。需要特别注意的是,叶节点的高度为 0 ,而空节点的高度为 -1

节点的「平衡因子 balance factor」定义为节点左子树的高度减去右子树的高度,同时规定空节点的平衡因子为 0 。节点平衡因子 = 左子树高度 - 右子树高度

AVL树旋转

旋转操作既能保持“二叉搜索树”的性质,也能使树重新变为“平衡二叉树”,将平衡因子绝对值 >1 的节点称为“失衡节点”,旋转操作分为四种:右旋、左旋、先右旋后左旋、先左旋后右旋。

右旋

找到失衡节点,记为node,其左子节点记为child,node节点执行“右旋”操作(在以child节点上为原点顺时针旋转90°),若child右节点存在,记为grandChild,则然后child代替原来node的位置,然后grandChild补在node的左节点上。【node顺转补左】

左旋

找到失衡节点,记为node,其右子节点记为child,node节点执行“左旋”操作(在以child节点上为原点逆时针旋转90°),若child左节点存在,记为grandChild,则然后child代替原来node的位置,然后grandChild补在node的右节点上。【node逆转补右】

可见,右旋和左旋操作在逻辑上是镜像对称的,它们分别解决的两种失衡情况也是对称的

左旋转后右旋

仅使用左旋或右旋都无法使子树恢复平衡。此时需要先对 child 执行“左旋”,再对 node 执行“右旋”。

右旋后左旋

对于上述失衡二叉树的镜像情况,需要先对 child 执行“右旋”,然后对 node 执行“左旋”。

四种旋转情况的选择条件:

失衡节点的平衡因子 子节点的平衡因子 应采用的旋转方法
>1 (即左偏树) >= 0 右旋
>1 (即左偏树) < 0 先左旋后右旋
<−1 (即右偏树) <= 0 左旋
<−1 (即右偏树) > 0 先右旋后左旋
#include
using namespace std;

// 平衡二叉搜索树 AVL

/* AVL 树节点结构体 */
struct TreeNode {
	int val{};          // 节点值
	int height = 0;     // 节点高度,跟最远叶节点的距离(边)
	TreeNode* left{};   // 左子节点
	TreeNode* right{};  // 右子节点
	TreeNode() = default;
	explicit TreeNode(int x) : val(x) {}
};

/* AVL 树 */
class AVLTree {

private:

	/* 更新节点高度 */
	void updateHeight(TreeNode* node) {
		// 节点高度等于最高子树高度 + 1,这个1是包括本身
		node->height = max(height(node->left), height(node->right)) + 1;
		cout << "updateHight = " << node->height << endl;
	}

	/* 右旋操作 */
	TreeNode* rightRotate(TreeNode* node) {
		TreeNode* child = node->left;
		TreeNode* grandChild = child->right;
		// 以 child 为原点,将 node 向右旋转
		child->right = node;
		node->left = grandChild;
		// 更新节点高度
		updateHeight(node);
		updateHeight(child);
		// 返回旋转后子树的根节点
		return child;
	}

	/* 左旋操作 */
	TreeNode* leftRotate(TreeNode* node) {
		TreeNode* child = node->right;
		TreeNode* grandChild = child->left;
		// 以 child 为原点,将 node 向左旋转
		child->left = node;
		node->right = grandChild;
		// 更新节点高度
		updateHeight(node);
		updateHeight(child);
		// 返回旋转后子树的根节点
		return child;
	}

	/* 执行旋转操作,使该子树重新恢复平衡 */
	TreeNode* rotate(TreeNode* node) {
		// 获取节点 node 的平衡因子
		int _balanceFactor = balanceFactor(node);
		// 左偏树
		if (_balanceFactor > 1) {
			//判断node左节点的平衡因子
			if (balanceFactor(node->left) >= 0) {
				// 右旋
				return rightRotate(node);
			}
			else {
				// 先左旋后右旋
				node->left = leftRotate(node->left);
				return rightRotate(node);
			}
		}
		// 右偏树
		if (_balanceFactor < -1) {
			if (balanceFactor(node->right) <= 0) {
				// 左旋
				return leftRotate(node);
			}
			else {
				// 先右旋后左旋
				node->right = rightRotate(node->right);
				return leftRotate(node);
			}
		}
		// 平衡树,无须旋转,直接返回
		return node;
	}

	/* 递归插入节点(辅助方法) */
	TreeNode* insertHelper(TreeNode* node, int val) {
		if (node == nullptr)
			return new TreeNode(val);
		/* 1. 查找插入位置,并插入节点 */
		if (val < node->val)
			node->left = insertHelper(node->left, val);
		else if (val > node->val)
			node->right = insertHelper(node->right, val);
		else
			return node;    // 重复节点不插入,直接返回
		updateHeight(node); // 更新节点高度
		/* 2. 执行旋转操作,使该子树重新恢复平衡 */
		node = rotate(node);
		// 返回子树的根节点
		return node;
	}

	/* 递归删除节点(辅助方法) */
	TreeNode* removeHelper(TreeNode* node, int val) {
		if (node == nullptr)
			return nullptr;
		/* 1. 查找节点,并删除之 */
		if (val < node->val)
			node->left = removeHelper(node->left, val);
		else if (val > node->val)
			node->right = removeHelper(node->right, val);
		else {
			//该节点的度为0或1
			if (node->left == nullptr || node->right == nullptr) {
				TreeNode* child = node->left != nullptr ? node->left : node->right;
				// 子节点数量 = 0 ,直接删除 node 并返回
				if (child == nullptr) {
					delete node;
					return nullptr;
				}
				// 子节点数量 = 1 ,直接删除 node
				else {
					delete node;
					node = child;
				}
			}
			else {
				// 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点
				TreeNode* temp = node->right;
				//node右子树的最小值(左节点上)
				while (temp->left != nullptr) {
					temp = temp->left;
				}
				int tempVal = temp->val;
				node->right = removeHelper(node->right, temp->val);
				node->val = tempVal;
			}
		}
		updateHeight(node); // 更新节点高度
		/* 2. 执行旋转操作,使该子树重新恢复平衡 */
		node = rotate(node);
		// 返回子树的根节点
		return node;
	}

public:
	TreeNode* root; // 根节点

	/*构造方法*/
	AVLTree() : root(nullptr) {
	}

	/*析构方法*/
	~AVLTree() {
		freeMemoryTree(root);
	}

	/* Free the memory allocated to a tree */
	void freeMemoryTree(TreeNode* root) {
		if (root == nullptr) return;
		freeMemoryTree(root->left);
		freeMemoryTree(root->right);
		// 释放内存
		delete root;
	}

	/* 获取节点高度 */
	int height(TreeNode* node) {
		// 空节点高度为 -1 ,叶节点高度为 0
		return node == nullptr ? -1 : node->height;
	}

	/* 获取平衡因子 */
	int balanceFactor(TreeNode* node) {
		// 空节点平衡因子为 0
		if (node == nullptr) return 0;
		// 节点平衡因子 = 左子树高度 - 右子树高度
		return height(node->left) - height(node->right);
	}

	/* 插入节点 */
	void insert(int val) {
		root = insertHelper(root, val);
	}

	/* 删除节点 */
	void remove(int val) {
		root = removeHelper(root, val);
	}

	/* 查找节点 */
	TreeNode* search(int val) {
		TreeNode* cur = root;
		// 循环查找,越过叶节点后跳出
		while (cur != nullptr) {
			// 目标节点在 cur 的右子树中
			if (cur->val < val)
				cur = cur->right;
			// 目标节点在 cur 的左子树中
			else if (cur->val > val)
				cur = cur->left;
			// 找到目标节点,跳出循环
			else
				break;
		}
		// 返回目标节点
		return cur;
	}

	//BFS遍历
	void BFS(TreeNode* root) {
		//初始化队列
		queue queue;
		queue.push(root);
		//保存遍历序列
		vectorvec;
		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);
		}
		cout << "BFS: ";
		for (int v : vec) cout << v << " ";
		cout << endl;
	}

};

int main() {
	
	AVLTree avlTree;

	avlTree.insert(1);
	avlTree.insert(0);
	avlTree.insert(4);
	avlTree.insert(3);
	avlTree.insert(5);
	avlTree.insert(6);

	avlTree.BFS(avlTree.root);
	
	return 0;
}

二叉树的遍历

二叉树常见的遍历方式包括层序遍历(BFS)、深度优先搜索(DFS)、前序遍历(根左右)、中序遍历(左根右)和后序遍历(左右根)等。前序、中序和后序遍历都属于「深度优先遍历 depth-first traversal」,它体现了一种“先走到尽头,再回溯继续”的遍历方式。

广度优先遍历BFS

「层序遍历 level-order traversal」从顶部到底部逐层遍历二叉树,并在每一层按照从左到右的顺序访问节点。层序遍历本质上属于「广度优先搜索 breadth-first Search,又称BFS」,它体现了一种“一圈一圈向外扩展”的逐层遍历方式。

深度优先搜索DFS

深度优先搜索通常基于递归实现,时间复杂O(n)

#include
using namespace std;

/* 二叉树节点结构体 */
struct TreeNode {
	int val;
	TreeNode* left;
	TreeNode* right;
	TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

//保存遍历序列
vectorvec;

//增加节点6
void addTreeNode(TreeNode* root, TreeNode* p) {
	TreeNode* n2 = root->left;
	root->left = p;
	p->left = n2;
	cout << "add node 6" << endl;
}

//删除节点6
void deleTreeNode(TreeNode* root) {
	TreeNode* p = root->left;
	TreeNode* n2 = p->left;
	root->left = n2;
	cout << "dele node 6" << endl;
}

//前序遍历 根左右
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);
}

//BFS遍历
void BFS(TreeNode* root) {
	//初始化队列
	queue queue;
	queue.push(root);
	//保存遍历序列
	vectorvec;
	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);
	}
	cout << "BFS: ";
	for (int v : vec) cout << v << " ";
	cout << endl;
}

int main() {
	/* 初始化二叉树 */
	TreeNode* n1 = new TreeNode(1);
	TreeNode* n2 = new TreeNode(2);
	TreeNode* n3 = new TreeNode(3);
	TreeNode* n4 = new TreeNode(4);
	TreeNode* n5 = new TreeNode(5);
	// 构建引用指向(即指针)
	n1->left = n2;
	n1->right = n3;
	n2->left = n4;
	n2->right = n5;
	BFS(n1);

	preOrder(n1);

	cout << "preOrder: ";
	for (int v : vec) cout << v << " ";
	cout << endl;

	vec.clear();
	inOrder(n1);

	cout << "inOrder: ";
	for (int v : vec) cout << v << " ";
	cout << endl;

	vec.clear();
	postOrder(n1);

	cout << "postOrder: ";
	for (int v : vec) cout << v << " ";
	cout << endl;

	TreeNode* addTree = new TreeNode(6);
	addTreeNode(n1, addTree);
	BFS(n1);

	deleTreeNode(n1);
	BFS(n1);

	return 0;
}

Heap堆

你可能感兴趣的:(算法,C++,c++,算法,数据结构)