2023年3月15日
由于昨天课多,并且写了一下作业,导致昨天没时间写日记了,今天就连着昨天和今天的一起补上
#二叉树的理论基础
在这里,我依然是按照自己所学的课本进行学习的,因为lc上的结点太简单了,所以这里不做阐述
首当其冲的是树的定义
①一棵树t是一个非空的有限元素的集合,其中一个元素为根(root),其余的元素(如果有的话)组成t的子树(subtree)。
②一棵树的高度或深度是树中级的个数
③一个元素的度是指其孩子的个数。叶节点的度数为0
④一棵树的度是其元素的度的最大值
再者是二叉树的定义:
一颗二叉树t是有限个元素的集合(可以为空)。当二叉树非空时,其中有一个元素称为根,余下的元素(如果有的话)被划分成两颗二叉树,分别称为t的左子树和右子树
注意,二叉树和树的根本区别是:①二叉树的每个元素都恰好有两颗子树(其中一个或两个可能为空)。而树的每个元素可有任意数量的子树②在二叉树中,每个元素的子树都是有序的,也就是说,有左子树和右子树之分,而树的子树是无序的③二叉树可以为空,但树不能为空
二叉树的特性如下:
①一颗二叉树有n个元素,n>0,它有n-1条边
②一颗二叉树的高度为h,h≥0,它最少有h个元素,最多有2^h-1个元素
③一颗二叉树有n个元素,n>0,它的高度最大为n,最小高度为向上取整(log2(n+1))
④当高度为h的二叉树恰好有2^h-1个元素时,称其为满二叉树
⑤对高度为h的满二叉树的元素,从第一层到最后一层,在每一次中从左到右顺序编号,这样的二叉树称为完全二叉树。满二叉树是完全二叉树的一个特例
⑥设完全二叉树的一元素其编号为i,1≤i≤n,有以下关系成立
a.如果i=1,则该元素为二叉树的根。若i>1,则其父节点的编号为向下取整(i/2)
b.如果2i>n,则该元素无左孩子,否则,其左孩子的编号为2i
c.如果2i+1>n,则该元素无右孩子,否则,其右孩子的编号为2i+1
#二叉树的ADT
template<class T>
class binaryTree
{
public:
virtual ~binaryTree() {}
virtual bool empty() const = 0;
virtual int size() const = 0;
virtual void preOrder(void (*) (T *)) = 0;
// parameter is a pointer to a function whose return
// type is void and has a single argument of type T*
virtual void inOrder(void (*) (T *)) = 0;
virtual void postOrder(void (*) (T *)) = 0;
virtual void levelOrder(void (*) (T *)) = 0;
};
二叉树的结构体如下
template <class T>
struct binaryTreeNode
{
T element;
binaryTreeNode<T> *leftChild, // left subtree
*rightChild; // right subtree
binaryTreeNode() {leftChild = rightChild = NULL;}
binaryTreeNode(const T& theElement):element(theElement)
{
leftChild = rightChild = NULL;
}
binaryTreeNode(const T& theElement,
binaryTreeNode *theLeftChild,
binaryTreeNode *theRightChild)
:element(theElement)
{
leftChild = theLeftChild;
rightChild = theRightChild;
}
};
整体的代码如下(其中的linkedBinaryTree代码过多,此处不再展示)
template<class E>
void linkedBinaryTree<E>::preOrder(binaryTreeNode<E> *t)
{// Preorder traversal.
if (t != NULL)
{
linkedBinaryTree<E>::visit(t);
preOrder(t->leftChild);
preOrder(t->rightChild);
}
}
template<class E>
void linkedBinaryTree<E>::inOrder(binaryTreeNode<E> *t)
{// Inorder traversal.
if (t != NULL)
{
inOrder(t->leftChild);
linkedBinaryTree<E>::visit(t);
inOrder(t->rightChild);
}
}
template<class E>
void linkedBinaryTree<E>::postOrder(binaryTreeNode<E> *t)
{// Postorder traversal.
if (t != NULL)
{
postOrder(t->leftChild);
postOrder(t->rightChild);
linkedBinaryTree<E>::visit(t);
}
}
template <class E>
void linkedBinaryTree<E>::levelOrder(void(*theVisit)(binaryTreeNode<E> *))
{// Level-order traversal.
arrayQueue<binaryTreeNode<E>*> q;
binaryTreeNode<E> *t = root;
while (t != NULL)
{
theVisit(t); // visit t
// put t's children on queue
if (t->leftChild != NULL)
q.push(t->leftChild);
if (t->rightChild != NULL)
q.push(t->rightChild);
// get next node to visit
try {t = q.front();}
catch (queueEmpty) {return;}
q.pop();
}
}
template <class E>
int linkedBinaryTree<E>::height(binaryTreeNode<E> *t)
{// Return height of tree rooted at *t.
if (t == NULL)
return 0; // empty tree
int hl = height(t->leftChild); // height of left
int hr = height(t->rightChild); // height of right
if (hl > hr)
return ++hl;
else
return ++hr;
}
#优先级队列
优先级队列说白了就是大根堆或者小根堆,这里具体概念不再阐述,只上代码
优先级队列的ADT
template<class T>
class maxPriorityQueue
{
public:
virtual ~maxPriorityQueue() {}
virtual bool empty() const = 0;
// return true iff queue is empty
virtual int size() const = 0;
// return number of elements in queue
virtual const T& top() = 0;
// return reference to the max element
virtual void pop() = 0;
// remove the top element
virtual void push(const T& theElement) = 0;
// add theElement to the queue
};
类maxHeap
template<class T>
class maxHeap : public maxPriorityQueue<T>
{
public:
maxHeap(int initialCapacity = 10);
~maxHeap() {delete [] heap;}
bool empty() const {return heapSize == 0;}
int size() const
{return heapSize;}
const T& top()
{// return max element
if (heapSize == 0)
throw queueEmpty();
return heap[1];
}
void pop();
void push(const T&);
void initialize(T *, int);
void deactivateArray()
{heap = NULL; arrayLength = heapSize = 0;}
void output(ostream& out) const;
private:
int heapSize; // number of elements in queue
int arrayLength; // queue capacity + 1
T *heap; // element array
};
template<class T>
maxHeap<T>::maxHeap(int initialCapacity)
{// Constructor.
if (initialCapacity < 1)
{ostringstream s;
s << "Initial capacity = " << initialCapacity << " Must be > 0";
throw illegalParameterValue(s.str());
}
arrayLength = initialCapacity + 1;
heap = new T[arrayLength];
heapSize = 0;
}
template<class T>
void maxHeap<T>::push(const T& theElement)
{// Add theElement to heap.
// increase array length if necessary
if (heapSize == arrayLength - 1)
{// double array length
changeLength1D(heap, arrayLength, 2 * arrayLength);
arrayLength *= 2;
}
// find place for theElement
// currentNode starts at new leaf and moves up tree
int currentNode = ++heapSize;
while (currentNode != 1 && heap[currentNode / 2] < theElement)
{
// cannot put theElement in heap[currentNode]
heap[currentNode] = heap[currentNode / 2]; // move element down
currentNode /= 2; // move to parent
}
heap[currentNode] = theElement;
}
template<class T>
void maxHeap<T>::pop()
{// Remove max element.
// if heap is empty return null
if (heapSize == 0) // heap empty
throw queueEmpty();
// Delete max element
heap[1].~T();
// Remove last element and reheapify
T lastElement = heap[heapSize--];
// find place for lastElement starting at root
int currentNode = 1,
child = 2; // child of currentNode
while (child <= heapSize)
{
// heap[child] should be larger child of currentNode
if (child < heapSize && heap[child] < heap[child + 1])
child++;
// can we put lastElement in heap[currentNode]?
if (lastElement >= heap[child])
break; // yes
// no
heap[currentNode] = heap[child]; // move child up
currentNode = child; // move down a level
child *= 2;
}
heap[currentNode] = lastElement;
}
template<class T>
void maxHeap<T>::initialize(T *theHeap, int theSize)
{// Initialize max heap to element array theHeap[1:theSize].
delete [] heap;
heap = theHeap;
heapSize = theSize;
// heapify
for (int root = heapSize / 2; root >= 1; root--)
{
T rootElement = heap[root];
// find place to put rootElement
int child = 2 * root; // parent of child is target
// location for rootElement
while (child <= heapSize)
{
// heap[child] should be larger sibling
if (child < heapSize && heap[child] < heap[child + 1])
child++;
// can we put rootElement in heap[child/2]?
if (rootElement >= heap[child])
break; // yes
// no
heap[child / 2] = heap[child]; // move child up
child *= 2; // move down a level
}
heap[child / 2] = rootElement;
}
}
template<class T>
void maxHeap<T>::output(ostream& out) const
{// Put the list into the stream out.
copy(heap + 1, heap + heapSize + 1, ostream_iterator<T>(cout, " "));
}
// overload <<
template <class T>
ostream& operator<<(ostream& out, const maxHeap<T>& x)
{x.output(out); return out;}
#左高树
考察一颗二叉树,它有一类特殊的结点叫做外部结点,它代替树中的空子树。其余结点叫做内部结点。一颗二叉树称为高度优先左高树,当且仅当其任何一个内部节点的左孩子的s值都大于或等于右孩子的s值。其中s是指节点x到其子树的外部节点的所有路径中最短的一条,若为内部节点,则取x的左右孩子的s值较小的那一个+1;若为外部节点,则为0
类maxHblt
template<class T>
class maxHblt : public maxPriorityQueue<T>,
public linkedBinaryTree<pair<int,T> >
{
public:
bool empty() const {return treeSize == 0;}
int size() const {return treeSize;}
const T& top()
{// return max element
if (treeSize == 0)
throw queueEmpty();
return root->element.second;
}
void pop();
void push(const T&);
void initialize(T *, int);
void meld(maxHblt<T>& theHblt)
{// meld *this and theHblt
meld(root, theHblt.root);
treeSize += theHblt.treeSize;
theHblt.root = NULL;
theHblt.treeSize = 0;
}
void output() {postOrder(hbltOutput); cout << endl;}
private:
void meld(binaryTreeNode<pair<int,T> >* &,
binaryTreeNode<pair<int,T> >* &);
static void hbltOutput(binaryTreeNode<pair<int,T> > *t)
{cout << t->element.second << ' ';}
};
template<class T>
void maxHblt<T>::meld(binaryTreeNode<pair<int, T> >* &x,
binaryTreeNode<pair<int, T> >* &y)
{// Meld leftist trees with roots *x and *y.
// Return pointer to new root in x.
if (y == NULL) // y is empty
return;
if (x == NULL) // x is empty
{x = y; return;}
// neither is empty, swap x and y if necessary
if (x->element.second < y->element.second)
swap(x, y);
// now x->element.second >= y->element.second
meld(x->rightChild,y);
// swap subtrees of x if necessary and set x->element.first
if (x->leftChild == NULL)
{// left subtree empty, swap the subtrees
x->leftChild = x->rightChild;
x->rightChild = NULL;
x->element.first = 1;
}
else
{// swap only if left subtree has smaller s value
if (x->leftChild->element.first < x->rightChild->element.first)
swap(x->leftChild, x->rightChild);
// update s value of x
x->element.first = x->rightChild->element.first + 1;
}
}
template<class T>
void maxHblt<T>::push(const T& theElement)
{// Insert theElement into the leftist tree.
// Create tree with one node.
binaryTreeNode<pair<int,T> > *q =
new binaryTreeNode<pair<int,T> > (pair<int,T>(1, theElement));
// meld q and original tree
meld(root,q);
treeSize++;
}
template<class T>
void maxHblt<T>::pop()
{// Delete max element.
if (root == NULL)
throw queueEmpty();
// tree not empty
binaryTreeNode<pair<int,T> > *left = root->leftChild,
*right = root->rightChild;
delete root;
root = left;
meld(root, right);
treeSize--;
}
template<class T>
void maxHblt<T>::initialize(T* theElements, int theSize)
{// Initialize hblt with theElements[1:theSize].
arrayQueue<binaryTreeNode<pair<int,T> >*> q(theSize);
erase(); // make *this empty
// initialize queue of trees
for (int i = 1; i <= theSize; i++)
// create trees with one node each
q.push(new binaryTreeNode<pair<int,T> >
(pair<int,T>(1, theElements[i])));
// repeatedly meld from queue
for (i = 1; i <= theSize - 1; i++)
{// pop and meld two trees from queue
binaryTreeNode<pair<int,T> > *b = q.front();
q.pop();
binaryTreeNode<pair<int,T> > *c = q.front();
q.pop();
meld(b,c);
// put melded tree on queue
q.push(b);
}
if (theSize > 0)
root = q.front();
treeSize = theSize;
}
关于二叉树的理论基础,代码随想录中给出的这里就不再阐述,这里只给出代码随想录中遍历二叉树的几种方式
#循环遍历(代码随想录)
前序遍历
class Solution {
public:
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
vec.push_back(cur->val); // 中
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root, result);
return result;
}
};
中序遍历
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
traversal(cur->left, vec); // 左
vec.push_back(cur->val); // 中
traversal(cur->right, vec); // 右
}
后序遍历
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
vec.push_back(cur->val); // 中
}
#迭代遍历(代码随想录)
前序遍历
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
if (root == NULL) return result;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top(); // 中
st.pop();
result.push_back(node->val);
if (node->right) st.push(node->right); // 右(空节点不入栈)
if (node->left) st.push(node->left); // 左(空节点不入栈)
}
return result;
}
};
中序遍历
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
TreeNode* cur = root;
while (cur != NULL || !st.empty()) {
if (cur != NULL) { // 指针来访问节点,访问到最底层
st.push(cur); // 将访问的节点放进栈
cur = cur->left; // 左
} else {
cur = st.top(); // 从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
st.pop();
result.push_back(cur->val); // 中
cur = cur->right; // 右
}
}
return result;
}
};
后序遍历
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
if (root == NULL) return result;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
st.pop();
result.push_back(node->val);
if (node->left) st.push(node->left); // 相对于前序遍历,这更改一下入栈顺序 (空节点不入栈)
if (node->right) st.push(node->right); // 空节点不入栈
}
reverse(result.begin(), result.end()); // 将结果反转之后就是左右中的顺序了
return result;
}
};
#统一的迭代法(代码随想录)
中序遍历
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
if (root != NULL) st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
if (node != NULL) {
st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
if (node->right) st.push(node->right); // 添加右节点(空节点不入栈)
st.push(node); // 添加中节点
st.push(NULL); // 中节点访问过,但是还没有处理,加入空节点做为标记。
if (node->left) st.push(node->left); // 添加左节点(空节点不入栈)
} else { // 只有遇到空节点的时候,才将下一个节点放进结果集
st.pop(); // 将空节点弹出
node = st.top(); // 重新取出栈中元素
st.pop();
result.push_back(node->val); // 加入到结果集
}
}
return result;
}
};
前序遍历
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
if (root != NULL) st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
if (node != NULL) {
st.pop();
if (node->right) st.push(node->right); // 右
if (node->left) st.push(node->left); // 左
st.push(node); // 中
st.push(NULL);
} else {
st.pop();
node = st.top();
st.pop();
result.push_back(node->val);
}
}
return result;
}
};
后序遍历
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
if (root != NULL) st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
if (node != NULL) {
st.pop();
st.push(node); // 中
st.push(NULL);
if (node->right) st.push(node->right); // 右
if (node->left) st.push(node->left); // 左
} else {
st.pop();
node = st.top();
st.pop();
result.push_back(node->val);
}
}
return result;
}
};
#层次遍历
简单来说,就是把节点弹出的时候,把该节点的左右孩子放入栈中
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> que;
if (root != NULL) que.push(root);
vector<vector<int>> result;
while (!que.empty()) {
int size = que.size();
vector<int> vec;
// 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
que.pop();
vec.push_back(node->val);
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
result.push_back(vec);
}
return result;
}
};
下面给出递归法的层序遍历
class Solution {
public:
void order(TreeNode* cur, vector<vector<int>>& result, int depth)
{
if (cur == nullptr) return;
if (result.size() == depth) result.push_back(vector<int>());
result[depth].push_back(cur->val);
order(cur->left, result, depth + 1);
order(cur->right, result, depth + 1);
}
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
int depth = 0;
order(root, result, depth);
return result;
}
};
#翻转二叉树 226. 翻转二叉树 - 力扣(LeetCode)
这道题的思路很简单,就是翻转每一个节点的左右子树就可以了
递归法如下
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) return root;
swap(root->left, root->right); // 中
invertTree(root->left); // 左
invertTree(root->right); // 右
return root;
}
};
迭代法如下
前序遍历
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) return root;
stack<TreeNode*> st;
st.push(root);
while(!st.empty()) {
TreeNode* node = st.top(); // 中
st.pop();
swap(node->left, node->right);
if(node->right) st.push(node->right); // 右
if(node->left) st.push(node->left); // 左
}
return root;
}
};
前序遍历2
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
stack<TreeNode*> st;
if (root != NULL) st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
if (node != NULL) {
st.pop();
if (node->right) st.push(node->right); // 右
if (node->left) st.push(node->left); // 左
st.push(node); // 中
st.push(NULL);
} else {
st.pop();
node = st.top();
st.pop();
swap(node->left, node->right); // 节点处理逻辑
}
}
return root;
}
};
层次遍历
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
queue<TreeNode*> que;
if (root != NULL) que.push(root);
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
que.pop();
swap(node->left, node->right); // 节点处理
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
}
return root;
}
};
#对称二叉树 101. 对称二叉树 - 力扣(LeetCode)
这道题一开始一直卡在怎么一个遍历顺序中,一直搞不明白,后来经过实际绘图发现了
class Solution {
public:
bool compare(TreeNode* left, TreeNode* right) {
if (left == NULL && right != NULL) return false;
else if (left != NULL && right == NULL) return false;
else if (left == NULL && right == NULL) return true;
else if (left->val != right->val) return false;
else return compare(left->left, right->right) && compare(left->right, right->left);
}
bool isSymmetric(TreeNode* root) {
if (root == NULL) return true;
return compare(root->left, root->right);
}
};
使用队列
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (root == NULL) return true;
queue<TreeNode*> que;
que.push(root->left); // 将左子树头结点加入队列
que.push(root->right); // 将右子树头结点加入队列
while (!que.empty()) { // 接下来就要判断这两个树是否相互翻转
TreeNode* leftNode = que.front(); que.pop();
TreeNode* rightNode = que.front(); que.pop();
if (!leftNode && !rightNode) { // 左节点为空、右节点为空,此时说明是对称的
continue;
}
// 左右一个节点不为空,或者都不为空但数值不相同,返回false
if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {
return false;
}
que.push(leftNode->left); // 加入左节点左孩子
que.push(rightNode->right); // 加入右节点右孩子
que.push(leftNode->right); // 加入左节点右孩子
que.push(rightNode->left); // 加入右节点左孩子
}
return true;
}
};
使用栈
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (root == NULL) return true;
stack<TreeNode*> st; // 这里改成了栈
st.push(root->left);
st.push(root->right);
while (!st.empty()) {
TreeNode* leftNode = st.top(); st.pop();
TreeNode* rightNode = st.top(); st.pop();
if (!leftNode && !rightNode) {
continue;
}
if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {
return false;
}
st.push(leftNode->left);
st.push(rightNode->right);
st.push(leftNode->right);
st.push(rightNode->left);
}
return true;
}
};
今天上午急急忙慌地算过了一遍,下午要实际上手操作一下,第一遍过的时候一定要基础打牢,这样到后面才能有思路
那今天就到这儿吧,明天见!