1.二叉树
二叉树是一种数据结构,它由节点和指向它们的边组成。每个节点最多有两个子节点,这就是为什么它被称为“二叉树”的原因。左子节点在树上表示比父节点小的数据,右子节点表示比父节点大的数据。每个节点都有一个值,可以是数字、字符或任何其他类型的数据。二叉树每个节点有两个子节点,像颗倒过来的树。
二叉树有点像链表,但没有链表头节点的设立,取而代之作为统领的是根(root),由根衍生出 其余节点。
如图
在细分的情况下,每一个节点都可看作一个根,有的缺少右子节点,有的两边节点都没,但它们都可看作一颗树。
节点类:
class Node
{
public:
Node* Left;
Node* Right;
string key;
string value;
Node(Node* Left, Node* Right,string key, string value)
{
this->Left = Left;
this->Right = Right;
this->key = key;
this->value = value;
}
};
二叉树查找类(主要函数)
class BinaryTree
{
private:
Node* root;
int N;
public:
BinaryTree()
{
root = NULL;
N = 0;
}
void put(string key, string value)
{
root = put(root, key, value);
}
Node* put(Node* node, string key, string value) //下文有解释
{
if (node == NULL)
{
N++;
Node *new_node= new Node(NULL, NULL, key, value);
return new_node;
}
int result = stoi(key) - stoi(node->key);
if (result == 0)
{
node->value = value;
return node;
}
if (result > 0)
{
node->Right = put(node->Right, key, value);
}
else
{
node->Left = put(node->Left, key, value);
}
return node;
}
……
按照二叉树的特性,左子节点上放比父节点小的数据,右子节点放比父节点大的数据,这里我用key值来表达左右子节点的大小,因此我们需要不断比较key值,此处我的key是string数据类型,利用stoi()函数可以转成整型(要加sstream头文件)。像踢足球一样,比根小就往左踢,反之往右踢,等于直接取代根,直到踢到没人的地方(节点为NULL)的地方,就把球放下(为NULL节点赋值)。
注意到返回值?当节点为NULL时就说明可以赋值了,并返回此节点,因为是节点把自己放入put函数,返回时要给自己赋值。
不是空节点结束的情况,还是一样,都是节点把自己放进put函数,回来时返回自己重复赋值没有影响,主要是迎合空节点赋值。
2.功能函数
最大值和最小值函数
string minKey()
{
Node* min_Node = minKey(root);
return min_Node->key;
}
Node* minKey(Node* node)
{
if (node->Left != NULL)
{
return minKey(node->Left);
}
else
{
return node;
}
}
string maxKey()
{
Node* n = maxKey(root);
return n->key;
}
Node* maxKey(Node* node)
{
if (node->Right != NULL)
{
return maxKey(node->Right);
}
else
{
return node;
}
}
还是根据二叉树的特性,最小值永远在最左左子节点,最大值永远在最右右子节点,一边倒的子节点树和只有根的树当我没说(doge。
接下来的三个遍历函数在结尾处有解释,
前序遍历函数(根,左,右):
Queue* preErgodic()
{
Queue* list = new Queue();
preErgodic(root, list);
return list;
}
void preErgodic(Node* node, Queue *list)
{
if (node == NULL)
{
return;
}
list->insert(node->key);
preErgodic(node->Left, list);
preErgodic(node->Right, list);
}
中序遍历函数(左,根,右):
Queue* midErgodic()
{
Queue* list = new Queue();
midErgodic(root, list);
return list;
}
void midErgodic(Node* node, Queue* list)
{
if (node == NULL)
{
return;
}
if (node->Left != NULL)
{
midErgodic(node->Left,list);
}
list->insert(node->key);
if (node->Right != NULL)
{
midErgodic(node->Right, list);
}
}
后序遍历函数(左,右,根):
Queue* afterErgodic()
{
Queue* list = new Queue();
afterErgodic(root, list);
return list;
}
void afterErgodic(Node* node, Queue* list)
{
if (node == NULL)
{
return;
}
if (node->Left != NULL)
{
afterErgodic(node->Left, list);
}
if (node->Right != NULL)
{
afterErgodic(node->Right, list);
}
list->insert(node->key);
}
这三个函数都借用了一个队列结构,把优先级高的值先传入队列中
总的来说,这几个遍历都是先左子节点后右子节点,根节点在其中穿插排序,于是,我们可以采用递归调用,先对左子节点向队列输入,用递归函数将其隔开,以达成优先级。
前序遍历:
先把根节点值录入队列,开始分开两路,左大子树,右大子树,递归调用挡着,先完成左子树才会进行右子树操作,其中递归调用又不断细分,细分到最后左子节点时,先录入节点,再把最后节点的左右子,但都为NULL,直接结束,终于可以对右子节点录入。
其余遍历也一样,建议上手画图直接明了,都可以理解为根来看待,分左根和右根,最后节点只是它的左右子节点是NULL而已。
实现验证:
int main()
{
BinaryTree tree;
tree.put("6", "name6");
tree.put("2", "name2");
tree.put("3", "name3");
tree.put("5", "name5");
tree.put("7", "name7");
tree.put("6", "name6");
Queue* list1 = tree.preErgodic();
cout << "前序遍历:" << endl;
list1->disList();
Queue* list2 = tree.midErgodic();
cout << "中序遍历:" << endl;
list2->disList();
Queue* list3 = tree.afterErgodic();
cout << "后序遍历:" << endl;
list3->disList();
}
结果:
前序遍历:
有5个节点
6
2
3
5
7
中序遍历:
有5个节点
2
3
5
6
7
后序遍历:
有5个节点
5
3
2
7
6
补队列处代码
#include <iostream>
#include "Nodes.h"
using namespace std;
//纯纯我偷懒,把链表那块函数复制过来用,把定义全挤到头文件里了,但这块不主要,主要还是看二叉树实现
class Queue
{
Nodes* head;
int N = 0;
public:
Queue()
{
//初始化头节点和数据
head = new Nodes("", NULL);
N = 0;
};
void clearList()
{
head = NULL;
N = 0;
}
bool isEmpty()
{
return N == 0;
}
int length()
{
return N;
}
string get(int i)
{
Nodes* n = head->next;
for (int index = 0; index < N; index++)
{
if (n->next == NULL)
{
return "NULL";
}
n = n->next;
}
return n->item;
}
void insert(int i, string t)
{
//先找到i的前一个节点
Nodes* pre = head;
for (int index = 0; index <= i - 1; index++)
{
pre = pre->next;
}
//找到i处的节点
Nodes* curr = pre->next;
//创建新节点,让其指向原i处节点,并让i-1处节点指向它
Nodes* new_node = new Nodes(t, curr);
new_node->next = curr;
pre->next = new_node;
N++;
}
void insert(string t)
{
Nodes* n = head;
while (n->next != NULL)
{
n = n->next;
}
Nodes* new_node = new Nodes(t, NULL);
n->next = new_node;
N++;
}
string remove(int i)
{
//先找到i的前一个节点
Nodes* pre = head;
for (int index = 0; index <= i; index++)
{
pre = pre->next;
}
//再让i-1处节点指向i+1处节点,并断掉原i节点的指向
Nodes* curr = pre->next;
Nodes* next_node = curr->next;
pre->next = next_node;
N--;
return curr->item;
}
int indexOf(string t)
{
Nodes* n = head->next;
for (int i = 0; n->next != NULL; i++, n = n->next)//i<n;
{
if (n->item == t)
{
return i;
}
}
return -1;
}
void disList()
{
cout << "有" << N << "个节点" << endl;
Nodes* n = head->next;
for (int i = 0; i < N; i++)
{
cout << n->item << endl;
n = n->next;
}
}
};
队列节点
#include <iostream>
using namespace std;
class Nodes
{
public:
string item;
Nodes* next;
Nodes(string item, Nodes* next)
{
this->item = item;
this->next = next;
};
};