文章目录
- 1. 前言
- 2. 概念
- 3 静态搜索结构
- 3.1 静态搜索表
- 3.2 顺序搜索表
- 3.2.1 基于有序顺序表和顺序搜索和折半搜索
- 4 二叉搜索树
- 4.1 搜索二叉树的类定义
- 4.2 搜索二叉树的搜索
- 4.3 搜索二叉树的插入
- 4.4 搜索二叉树的删除
- 5 AVL树
- 5.1 平衡化旋转
- 5.1.1 右旋:LL型状态
- 5.1.2 左旋:RR型状态
- 5.1.3 右旋(LL)的例子
- 5.1.4 先左旋再右旋(LR)的操作
- 5.1.5 先右旋再左旋(RL)的操作
- 5.1.6 RL操作的例子
- 5.1.7 代码实现
- 5.2 AVL树的插入
- 5.3 AVL树的删除
- 6 伸展树
- 6.1 插入
- 6.2 查找
- 6.3 删除
- 7 红黑树
- 7.1 红黑树搜索
- 7.2 插入和删除
1. 前言
本系列笔记基于 清华大学出版社的《数据结构:用面向对象方法与C++语言描述》第二版进行学习。
2. 概念
使用基于关键码的搜索,搜索结果应该是唯一的。使用基于属性的搜索方法,搜索结果可能不唯一。
搜索的环境有两种,静态环境和动态环境。区别在于插入和删除后搜索结构会不会变化。
衡量搜索的时间效率的标准是:
在搜索过程中关键码的平均比较次数或平均读写磁盘次数,成为平均搜索长度ASL。
3 静态搜索结构
3.1 静态搜索表
最简单的基于数组的数据表类。
#include <iostream>
#include<assert.h>
using namespace std;
const int defaultSize = 100;
class dataList;class dataNode {friend class dataList;
public:dataNode(const int x = 99):key(x),data(0){}int getKey() const { return key; }void setKey(int k) { key = k; }
private:int key; // 关键码int data;
};class dataList {
public:dataList(int sz = defaultSize) :ArraySize(sz), CurrentSize(0) {Element = new dataNode[sz];assert(Element != NULL);}dataList(dataList& R);virtual ~dataList() { delete[]Element; } virtual int Length() { return CurrentSize; } virtual int getKey(int i)const {// 提取第i个元素值(从1开始)assert(i > 0 || i <= CurrentSize);return Element[i - 1].key;} virtual void setKey(int x, int i) { // 修改第i个元素的值assert(i > 0 || i <= CurrentSize);Element[i - 1].key = x;}virtual int SeqSearch(const int x) const; // 搜索virtual bool Insert(int& e1); // 插入virtual bool Remove(const int x, int& e1); // 删除friend ostream& operator<<(ostream& out, const dataList& OutList);friend ostream& operator>>(istream in, dataList& InList);
protected:dataNode* Element;int ArraySize;int CurrentSize;
};class searchList :public dataList {
public:searchList(int sz = defaultSize) :dataList(sz){}virtual int SeqSearch(const int x)const;
};int main()
{std::cout << "Hello World!\n";
}bool dataList::Insert(int& e1)
{if (CurrentSize == ArraySize) return false; // 表满,无法插入Element[CurrentSize] = e1;CurrentSize++;return true;
}bool dataList::Remove(const int x, int& e1)
{if (CurrentSize == 0)return false;int i;for (i = 0; i < CurrentSize && Element[i].key != x; i++);if (i == CurrentSize) return false;e1 = Element[i].data;Element[i] = Element[CurrentSize - 1];CurrentSize--; return true;
}ostream& operator<<(ostream& out, const dataList& OutList)
{cout << "Array Contents:\n";for (int i = 1; i < OutList.CurrentSize; i++) {cout << OutList.CurrentSize; i++;}cout << endl;cout << "Array Current Size:" << OutList.CurrentSize << endl;return out;
}ostream& operator>>(istream in, dataList& InList)
{cout << "Enter array Current Size:" << endl;in >> InList.CurrentSize;cout << "Enter array Elements:\n";for (int i = 1; i <= InList.CurrentSize; i++) {cout << "Element" << i << ":";in >> InList.Element[i - 1];}return in;
}
编译不过,因为Elements中没有设置接触到数据的函数,可以自行添加
3.2 顺序搜索表
int searchList::SeqSearch1(const int x, int loc) const
{if (loc > CurrentSize) return 0;else if (Element[loc - 1].key == x) return loc;else return SeqSearch1(x, loc + 1);
}
3.2.1 基于有序顺序表和顺序搜索和折半搜索
①顺序搜索
class SortedList : public searchList {
public:SortedList(int sz=100):searchList(sz){}~SortedList(){}int SequentSearch(const int x)const; // 顺序搜索int BinarySearch(const int x)const; // 折半搜索bool Insert(const int& e1); // 插入int Begin() { return (CurrentSize == 0) ? 0 : 1; } // 定位第一个int Next(int i) { return(i >= 1 && i <= CurrentSize) ? i + 1 : 0; } // 定位下一个
};int SortedList::SequentSearch(const int x) const
{for (int i = 1; i <= CurrentSize; i++)if (Element[i - 1].key == x) return i; // 成功,停止搜索else if (Element[i - 1].key > x)break; // 不成功,停止搜索return 0;
}
②折半搜索
折半搜索又称二分搜索法
非递归算法
int SortedList::BinarySearch(const int x) const
{// 非递归算法int high = CurrentSize - 1;int low = 0;int mid;while (low <=high) // 如果没有,最后搜索的一次是low<=high{mid = (low + high) / 2;if (x > Element[mid].key) low = mid + 1; // 把中间的元素的后一位变成lowelse if (x < Element[mid].key) high = mid - 1; // 把中间元素的前一位变成highelse return mid + 1;}return 0;
}
递归算法
int SortedList::BinarySearch1(const int x, int low, int high) const
{int mid = 0;if (low <= high) {mid = (low + high) / 2;if (x > Element[mid - 1].key)mid = BinarySearch1(x, mid + 1, high); else if (x < Element[mid - 1].key)mid = BinarySearch1(x, low, mid - 1);}return mid;
}
③插入算法
void SortedList::Insert(const dataNode& e1)
{assert(CurrentSize == ArraySize);int i = 1;while (i <= CurrentSize && Element[i - 1].key <= e1.key)i++; // 找到小于插入的结点的最大的结点for (int j = CurrentSize; j >= i; j--) Element[j] = Element[j - 1]; // 前面的赋值给另一个Element[i - 1] = e1;CurrentSize ++;
}
4 二叉搜索树
简单来说,就是如果找到二叉树的中序遍历,他的中序遍历一定是从小到大排序的
如上图,其中序遍历均位123
4.1 搜索二叉树的类定义
struct BSTNode
{int data; // 二叉树节点类BSTNode* left, * right; // 左右子女BSTNode():left(NULL),right(NULL){}BSTNode(const int d, BSTNode* L = NULL, BSTNode*R =NULL):data(d),left(L),right(R){}~BSTNode(){}void SetData(int d) { data = d; }int getData() { return data; }
};class BST {
public:BST() :root(NULL) {} BST(int Value); ~BST() {};bool Search(const int x) {return (search(x, root) != NULL) ? true : false;}BST& oprator = (const BST & R); // 赋值void makeEmpty() { makeEmpty(root); root = NULL; }void PrintTree()const { PrintTree(root); }int Min() { return Min(root)->data; }int Max() { return Max(root)->data; }bool Insert(const int& e1) { return Insert(e1, root); }bool Remove(const int x) { return Remove(x, root); }private:BSTNode* root;int RefValue; // 停止标志BSTNode* search(const int x, BSTNode* ptr); // 搜索void makeEmpty(BSTNode*& ptr); // 置空void PrintTree(BSTNode* ptr)const; // 打印BSTNode* Copy(const BSTNode* ptr); // 复制BSTNode* Min(BSTNode* ptr)const; // 求最小BSTNode* Max(BSTNode* ptr)const; // 求最大bool Insert(const int& e1, BSTNode*& ptr); // 递归插入bool Remove(const int x, BSTNode*& ptr); // 删除
};
4.2 搜索二叉树的搜索
超级好理解
BSTNode* BST::search(const int x, BSTNode* ptr)
{if (ptr == NULL) return NULL;else if (x < ptr->data) return search(x, ptr->left);else if (x > ptr->data) return search(x, ptr->right);else return ptr;
}
4.3 搜索二叉树的插入
bool BST::Insert(const int& e1, BSTNode*& ptr)
{if (ptr == NULL) {ptr = new BSTNode(e1);if (ptr = NULL) { cout << "out of space" << endl; exit(1); }}else if (e1 < ptr->data) Insert(e1, ptr->left); // 比结点小存左边else if (e1 > ptr->data)Insert(e1, ptr->right); // 比节点小存右边else return false;
}
4.4 搜索二叉树的删除
bool BST::Remove(const int x, BSTNode*& ptr)
{BSTNode* temp;if (ptr != NULL) {if (x < ptr->data) Remove(x, ptr->left); // 左子树执行删除else if (x > ptr->data) Remove(x, ptr->right); // 在右子树执行删除else if (ptr->left != NULL && ptr->right != NULL) { // 有两个子女节点temp = ptr->right; // 右子树搜寻中序的第一个结点while (temp->left != NULL) temp = temp->left;ptr->data = temp->data; // 用该节点数据代替根节点数据Remove(ptr->data, ptr->right);}else { // 只有一个子女结点(左子结点或右子结点) 这部分是为了链接删除部分缺掉的那条线temp = ptr;if (ptr->left == NULL) ptr = ptr->right;else ptr = ptr->left;delete temp;return true;}}return false;}
5 AVL树
任一结点的右子树的高度减去左子树的高度差只能为-1,0,1
5.1 平衡化旋转
使不平衡的二叉搜索树变得平衡
先说插入
这里有四种情况
5.1.1 右旋:LL型状态
就是插入根节点的子结点的左子结点,
右旋相当于把树往顺时针旋转以下,把B点做成根结点,再把B结点的右子结点变成原根节点的左子节点,
旋转后变成:
5.1.2 左旋:RR型状态
左旋和右旋差不多,就是插入了根节点的右子结点的右子树中。
左旋相当于将整个树逆时针旋转了一下,将C点置为根结点,然后将A点作为C点的左子树,把原来的左子树3作为原根节点的右子树
5.1.3 右旋(LL)的例子
本次是插入0到原来的平衡树中,如图,属于LL情况,所以进行右旋操作。
找到不平衡的根结点4。所以我们要调整的树就是
将原有的左子树,以2为根节点的树往上提,作为这棵树的新根节点,并把原有根节点4作为2的右子树,把2的原右子变为原根节点4的左子树
再把2作为原来根节点4,进行连接
5.1.4 先左旋再右旋(LR)的操作
元素落到56都是一样的处理,把
D转换成新的根结点,把原有不平衡树的根节点A的左子女结点作为D的左子女结点,根节点A作为D的右子女结点。并把D的左子树当作B的右子树,把D的右子树当作A的左子树。
5.1.5 先右旋再左旋(RL)的操作
插入56所作的操作都一样,原理与LR差不多,把D作为新的根节点,把不平衡树的根节点A作为D的左子树,A的右子女结点C作为D的右子树。并把D的左子树转换成A的右子树,D的右子树转换为C的左子树。
5.1.6 RL操作的例子
把4当作新父结点,将不平衡树的根节点2作为4的左子树,根节点的右结点5作为4的右子树。把4的左子树赋给2作为其右子女结点。
结果
5.1.7 代码实现
void AVLTree::RotateL(AVLNode*& ptr)
{// 右子树高RR型状态AVLNode* subL = ptr; // 要旋转的结点,即不平衡的树的根结点ptr = subL->right; // 原根节点的右结点subL->right = ptr->left; // 原根子树的右子女转化成其右结点的左子树ptr->left = subL; // 把原根节点作为原根节点右结点的左子树,此时ptr为新根节点ptr->bf = subL->bf = 0;}void AVLTree::RotateR(AVLNode*& ptr)
{// 左子树高LL型状态AVLNode* subR = ptr;ptr = subR->left;subR->left = ptr->right;ptr->right = subR;ptr->bf = subR->bf = 0;
}
void AVLTree::RotateRL(AVLNode*& ptr)
{AVLNode* subL = ptr; // 最后根节点的左子树AVLNode* subR = subL->right; // 最后根节点的右子树ptr = subR->left; subR->left = ptr->right; // subR的左子树变为其左子女节点的左子女节点ptr->right = subR; // 新根节点的右子女结点变为最终右子女结点if (ptr->bf >= 0) subR->bf = 0;else subR->bf = 1;subL->right = ptr->left; // subL的右子女变为新根节点的左子女结点ptr->left = subL; // 新根结点的变为最终的左子女结点if (ptr->bf == 1) subL->bf = -1;else subL->bf = 0;ptr->bf = 0;
}
void AVLTree::RotateLR(AVLNode*& ptr)
{AVLNode* subR = ptr;AVLNode* subL = subR->left;ptr = subL->right;subL->right = ptr->left;ptr->left = subL;if (ptr->bf <= 0) subL->bf = 0;else subL->bf = -1;subR->left = ptr->right;ptr->right = subR;if (ptr->bf = -1) subR->bf = 1;else subR->bf = 0;ptr->bf = 0;
}
5.2 AVL树的插入
沿着其插入路线查各结点的平衡度
各结点会有三种情况
①bf = 0,不需要处理,结束平衡化
②bf = 1,不需要处理,继续回溯
③bf = 2,bf = 2,右子树高,需要结合右子女q的bf做处理
1)q的bf = 1,执行左单旋转,插入位置为RR
2)q的bf=-1,执行先右后左旋转,插入位置为RL
bf=-2,左子树高,需要结合左子女q的bf做处理
1)q的bf=1,执行右单旋转,插入位置为LL
2) q的bf=-1,执行先右旋后左旋,插入位置为LR
bool AVLTree::Insert(AVLNode*& ptr, int& e1)
{AVLNode* pr = NULL;AVLNode* p = ptr;AVLNode* q;int d;Stack st;while (p!=NULL){if (e1 == p->data) return false; // 找到了e1的结点,不插入pr = p;st.push(pr); // 用栈记录查找路径,并找到插入的父结点prif (e1 < p->data) p = p->left;else p = p->right;}p = new AVLNode(e1);if (p == NULL) { cout << "error when allocate memory!"; exit(1); }if (pr == NULL) { ptr = p; return true; }if (e1 < pr->data) pr->left = p;else pr->right = p; // 新结点插入while (st.Isempty() == false) { // 重新平衡化st.Pop(pr); // 调查父结点的平衡因子 if (p == pr->left) pr->bf--; // 如果插入左边,则平衡因子-1else pr->bf++; // 插入右边则+1,平衡因子高度右边-左边if (pr->bf == 0) break; // 第一种情况,平衡退出if (pr->bf == 1 || pr->bf == -1)p = pr; // 第二种情况,继续向上搜寻else { // 第三种情况,|bf| =2;d = (pr->bf < 0) ? -1 : 1; // 区别单双旋if (p->bf == d) { // 两节点的平衡因子同号,单旋转if (d == -1) RotateR(pr); // bf ==-1else RotateL(pr); // bf =1}else {if (d == -1) RotateLR(pr);else RotateRL(pr);}break;}if (st.IsEmpty() == true) ptr = pr; // 调整到树的根节点else {st.getTop(q);if (q->data > pr->data) p->left = pr;else q->right = pr;}}return true;
}
这是>>和<<的重载:
istream& operator>>(istream in, AVLTree& Tree)
{int item;cout << "Construct AVL tree:\n";cout << "Input Data(end with" << Tree.RefValue << "):";in >> item;while (item != Tree.RefValue) {Tree.Insert(item);cout << "Input Data(end with" << Tree.RefValue << "):";in >> item;}return in;
}ostream& operator<<(ostream& out, const AVLTree& Tree)
{out << "Inorder Traversal of AVL tree.\n";Tree.Traverse(Tree.root, out); // 以中序次序输出个各节点的数据out << endl;return out;
}
看懂这个基本就学会了插入了
5.3 AVL树的删除
AVL树的删除算法与二叉搜索树类似,删除后如果破坏平衡性质,还需要做旋转
(1)如果被删结点p有两个子女,p的中序次序下找到直接前驱q,把q的内容传给p,把结点q当作被删结点p,他是只有一个子女的结点,此时看第二种情况
(2)如果被删结点p最多只有一个子女q。可以当p的父结点pr中原来指向p的指针改到q,如果结点p没有子女,p父结点pr的相应指针置位NULL。将原来的结点pr为根的子树的高度-1,并沿pr通向根的路径考量一路上各个结点的影响。
考查结点q的父结点pr。如果q是pr的左子女,则因子bf+1,否则bf-1
此时有三种情况
【1】bf的原来的平衡因子为0,则不需要调整了,结束重新平衡
【2】bf不为0,需要考察结点pr的父结点的平衡状态,
【3】需要平衡化
考察父结点pr的更高的子树的根为q(未被缩短的树),根据q的平衡因子,有三种操作
①如果q的平衡因子为0,执行一个单旋(未被缩短的树,结束后可以结束平衡的过程
②如果q的平衡因子与pr的平衡因子正负号相同,则执行一个单旋来恢复,结束后需要沿上继续评估平衡状态,相当LL型和RR型
③如果pr与q的平衡因子正负号相反,则需要执行双旋q,相当于LR和RL型,同时继续向上评估
bool AVLTree::Remove(AVLNode*& ptr, int x, int& e1)
{// 删除关键码为x的结点AVLNode* pr = NULL;AVLNode *p = ptr;AVLNode* q;AVLNode* ppr;int d;int dd = 0;Stack* st;while (p != NULL) {// 寻找删除位置if (x == p->data) break;pr = p;st.push(pr);if (x < p->data) p = p->left;else p = p->right;}if (p == NULL) return false; // 未找到,删除if (p->left != NULL && p->right != NULL) { // 有两各子女的情况pr = p;st.push(pr);q = p->left; // 再p左子树找到中序遍历直接前驱while (q->right != NULL) {pr = q;st.push(pr); q = q->right; }p->data = q->data; // 用q的值填补pp = q; // 被删结点转为q,此时是只有一个子女结点q的情况}if (p->left != NULL) q = p->left; // 被删结点只有一个子女else q = p->right; // 找到这个子女if (pr == NULL) ptr = q; // 被删结点为根节点else { // 被删结点不是根节点if (pr->left == p) pr->left = q; // 把父结点连接到被删结点的子女结点else pr->right = q;while (st.IsEmpty()== false) // 重新平衡化{st.Pop(pr); if (pr->right == q) pr->bf--; // 调整父结点的平衡因子else pr->bf++;if (st.IsEmpty() == false) {st.getTop(ppr);dd = (ppr->left == pr) ? -1 : 1; // 旋转后和上层连接方向}else dd = 0; // 旋转后不与上层连接if (pr->bf == 1 || pr->bf == -1) break; // 不需要旋转,直接退出if (pr->bf != 0) {if (pr->bf < 0) { d = -1; q = pr->left; }else { d = 1; q = pr->right; }if (q->bf == 0) {if (d == -1) {RotateR(pr);pr->bf = 1;pr->left->bf = -1;}else {RotateL(pr);pr->bf = -1;pr->right->bf = 1;}break;}if (q->bf == d) {if (d == -1) RotateR(pr);else RotateL(pr);}else {if (d == -1) RotateLR(pr);else RotateRL(pr);}if (dd == -1) ppr->left = pr;else if (dd == 1) ppr->right = pr;}q = pr;}if (st.IsEmpty() == true) ptr = pr;}delete p;return true;
}
不懂这段代码,真的很痛苦看的我
6 伸展树
每次插入结点,所有结点都要调整到根结点。
伸展树
6.1 插入
插入位置为根的左节点,右旋
插入位置为根的右结点,左旋
最左边LL情况,对中间结点进行右旋,在对结点进行右旋,直到变成根节点
最右边RR情况,对中间结点进行左旋,在对结点进行左旋,直到变成根节点
LR情况,中间结点先左旋后右旋,再对结点左旋右旋,直到变为根节点
RL情况,中间结点先右旋后左旋,再对结点右旋左旋,直到变为根节点
6.2 查找
插入和查找操作,查找后把查找到的结点变为根节点,情况跟插入类似
6.3 删除
和二叉搜索树相同,但需要把被删除结点的父结点展开到根节点。
7 红黑树
1)外部结点是黑色(空指针们
2)没有两个连续结点是红色,(可以两个连续结点是黑色
3)根到外部节点的路径上都有相同的黑色结点
也可以从指针看
7.1 红黑树搜索
和二叉搜索树完全相同
7.2 插入和删除
这个书上标**了,我这边先不学,看了下内容挺多的,后面再补