1.二叉树遍历
#include <iostream>
//STL标准库
#include<stack>
#include<queue>
using namespace std;
#define ElemType char
//树结点结构
struct TreeNode
{
ElemType data;
struct TreeNode* lchild=NULL;
struct TreeNode* rchild=NULL;
}*head;
//用于后序遍历
typedef struct node1
{
BinTree *btnode;
bool isFirst;
}BTNode;
int index = 0;
//先序遍历构造二叉树
// data[]={ 'A', 'B', 'D', '#', '#', 'E'}
void treeConstruct(TreeNode *&root, ElemType data[]){ ElemType e = data[index++]; if (e == '#'){ root = NULL; }
else{
root = new TreeNode;
root->data = e;
treeConstruct(root->lchild, data);
treeConstruct(root->rchild, data);
}
}
二叉树深度优先遍历(非递归)
A:先序遍历(先压右子树,再压左子树,根据栈后进先出,最终出栈顺序根左右)
//二叉树的深度优先遍历(非递归),借助栈(示例为先序遍历)
//https://www.cnblogs.com/fengxmx/p/3764512.html(先中后递归及非递归遍历参考)
void depthFirstSearch(TreeNode* root){
stack<TreeNode *> nodestack;
nodestack.push(root);
TreeNode *node = nullptr;
while (!nodestack.empty()){
node = nodestack.top();//指针访问栈顶元素
cout << node->data<<' ';
nodestack.pop();
}
if (node->rchild){
nodestack.push(root->rchild);//右子树压入栈
}
if (node->lchild){
nodestack.push(root->lchild);//左子树压入栈
}
}
B:先序遍历(判断栈是否为空或子树为空,若不为空,就对左孩子入栈并访问(根节点),直至左孩子为空,若左孩子为空,就出栈,然后对右孩子循环前面的操作)
/*二叉树的非递归先序遍历*/
void inorderRecur(TreeNode* root){
if (root == NULL) return;
stack<TreeNode*> nodestack;
TreeNode* p = root;
nodestack.push(root);
while (!nodestack.empty()){
if (p != NULL){
nodestack.push(p);
cout << p->data << ' ';
p = p->lchild;
}
else{
TreeNode*q = nodestack.top();
nodestack.pop();
p = q->rchild;
}
}
}
中序遍历(非递归):判断栈是否为空或子树为空,若不为空,就对左孩子入栈,直至左孩子为空,若左孩子为空,就出栈并访问(左孩子与根节点),然后对右孩子循环前面操作
//树的中序遍历
void inorderRecur(TreeNode* root){
if (root == NULL) return;
stack<TreeNode*> nodestack;
TreeNode* p = root;
nodestack.push(root);
while (!nodestack.empty()){
if (p != NULL){//直至左孩子为空
nodestack.push(p);
p = p->lchild;
}
else{
TreeNode*q = nodestack.top();
nodestack.pop();
cout << q->data << ' ';
p = q->rchild;
}
}
}
后序遍历(非递归):要保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点P,先将其入栈。如果P不存在左孩子和右孩子,则可以直接访问它;或者P存在左孩子或者右孩子,但是其左孩子和右孩子都已被访问过了,则同样可以直接访问该结点。若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在根结点前面被访问
思路二:类似先序遍历和中序遍历的思想,沿左子树一直往下搜索,直至出现没有左子树的结点,记下前驱结点(根结点),并判断父节点(当前前驱)是否第一次被访问,若是第一次被访问,将父节点入栈,父节点的右子树入栈,访问右子树,访问父节点(第二次出现在栈顶)
void postOrder2(BinTree *root) //非递归后序遍历
{
stack<BTNode*> s;
BinTree *p=root;
BTNode *temp;
while(p!=NULL||!s.empty())
{
while(p!=NULL) //沿左子树一直往下搜索,直至出现没有左子树的结点
{
BTNode *btn=(BTNode *)malloc(sizeof(BTNode));
btn->btnode=p;
btn->isFirst=true;
s.push(btn);
p=p->lchild;
}
if(!s.empty())
{
temp=s.top();
s.pop();
if(temp->isFirst==true) //表示是第一次出现在栈顶
{
temp->isFirst=false;
s.push(temp);
p=temp->btnode->rchild;
}
else //第二次出现在栈顶
{
cout<<temp->btnode->data<<" ";
p=NULL;
}
}
}
}
二叉树广度优先遍历
//二叉树广度优先遍历
void breadFirstSearch(TreeNode *root){
queue<TreeNode*> nodeQueue;
nodeQueue.push(root);
TreeNode * node=nullptr;
while (!nodeQueue.empty()){
node = nodeQueue.front();
nodeQueue.pop();
cout << node->data << ' ';
}
if (node->lchild){
nodeQueue.push(node->lchild);
}
if (node->rchild){
nodeQueue.push(node->rchild);
}
}
int main() {
//上图所示的二叉树先序遍历序列,其中用'#'表示结点无左子树或无右子树
ElemType data[6] = { 'A', 'B', 'D', '#', '#', 'E', '#' };
TreeNode *tree;
treeConstruct(tree, data);
printf("深度优先遍历二叉树结果: ");
depthFirstSearch(tree);
printf("\n\n广度优先遍历二叉树结果: ");
breadFirstSearch(tree);
return 0;
}
2.图的遍历
2.1 图的存储
参考链接:https://blog.csdn.net/dengpei187/article/details/51899550
1)邻接矩阵
typedef struct{
VertexType vexes[MAX]; //顶点表
EdgeType arc[MAX][MAX]; //邻接矩阵
int numVertexes,numEdges; //顶点数,边数
} MGraph;
无向网图的创建实现:
/*构建**无向网图**的邻接矩阵表示*/
void CreateMGraph(MGraph *G){
int i,j,k,w;
printf("输入顶点数和边数:\n");
scanf("%d,%d",&G->numVertexes,&G->numEdges); //输入顶点数和边数
for(i=0;i<G->numVertexes;i++)
scanf(&G->vexs[i]); //读入顶点信息,建立顶点表
for(i=0;i<G->numVertexes;i++)
for(j=0;j<G->numVertexes;j++)
G->arc[i][j]=INFINITY; //邻接矩阵初始化
for(k=0;k<G->numEdges;k++){ //给边表赋值
printf("输入边(vi,vj)上的下标i,下标j和权值w:\n");
scanf("%d,%d,%d",&i,&j,&w); //输入边(vi,vj)上的权值w
G->arc[i][j]=w;
G->arc[j][i]=G->arc[i][j]; //无向图,矩阵对称
}
}
n个顶点和e条边的无向网图创建的时间复杂度为 O(n+n^2+e) ,其中,对邻接矩阵的初始化耗费了 O(n^2) 的时间。
2) 邻接表
typedef char VertexType; //顶点类型
typedef int EdgeType; //边上的权值类型
typedef struct EdgeNode{ //边表结点
int adjvex;
EdgeType weight;
struct EdgeNode *next;
} EdgeNode;
typedef struct VertexNode{ //顶点表结点
VertexType data;
EdgeNode *firstedge;
} VertexNode, AdjList[MAX];
typedef struct{
AdjList adjList;
int numVertexes,numEdges;
} GraphAdjList;
构建邻接表实现:
/*建立无向图图的邻接表结构,算法的空间复杂度为O(n+e)*/
void CreateGraphAL(GraphAdjList *G){
int i, j, k;
EdgeNode *e;
printf("请输入顶点数和边数(输入格式为:顶点数,边数):\n");
// 读入顶点数和边数
scanf("%d,%d", &(G->numVertexes), &(G->numEdges));
for (i = 0; i < G->numVertexes; i++){// 读入顶点信息,建立顶点表
scanf("\n%c", &(G->adjList[i].data)); // 读入顶点信息
G->adjList[i].firstedge = NULL; // 边表头指针设为空
}
for (k = 0; k < G->numEdges; k++){ // 建立边表
printf("请输入边的信息(输入格式为:i,j):\n");
scanf("%d,%d", &i, &j); // 读入边(Vi,Vj)的顶点对应序号
/*相当于将j的结点头插法插入到顶点i的边表链中*/
e=(EdgeNode *)malloc(sizeof(EdgeNode)); //生成新边表结点
e->adjvex = j; // 邻接点序号为j
// 将新边表结点s插入到顶点Vi的边表头部
e->next = G->adjList[i].firstedge;
G->adjList[i].firstedge = e;
/*相当于将i的结点头插法插入到顶点j的边表链中*/(无向图分别为两个顶点插入边)
e=(EdgeNode *)malloc(sizeof(EdgeNode));
e->adjvex = i;
e->next = G->adjList[j].firstedge;
G->adjList[j].firstedge = e;
}
}
3)图各种存储结构小结
存储结构 | 空间复杂度 | 时间复杂度 | 特点 |
---|---|---|---|
链接矩阵 | o(n^2) | o(n^2) | 顺序存储(两个数组),方便查找访问,不方便插入、删除顶点; |
邻接表 | o(n+e) | o(n) | 数组+链表存储,适合边较稀疏的图,可查找某节点出度或入度(逆邻接表); |
十字链表 | o(n+e) | o(n) | 数组+链表,适合有向图的存储,方便同时查找某节点出度和入度; |
邻接多重表 | – | – | 适合无向图的链式存出结构,方便求两个顶点是否存在边(一条边存于一个节点中); |
2.2 图的深度优先遍历(DFS)
参考链接:https://blog.csdn.net/sunlilan/article/details/69829195
存储结构为邻接矩阵:
typedef int Boolean;
Boolean visited[MAX];
void DFS(MGraph G,int i){
int j;
visited[i]=TRUE;
printf("%c ",G.vexs[i]); //打印顶点,也可以进行其他操作
for(j=0;j<G.numVertexes;j++){
if(G.arc[i][j]==1 && !visited[j]) DFS(G,j); //对未访问的邻接顶点递归调用
}
}
void DFSTraverse(MGraph G){
int i;
for(i=0;i<G.numVertexes;i++){
visited[i]=FALSE; //初始化所有顶点状态为未访问
}
for(i=0;i<G.numVertexes;i++){
if(!visited[i]){ //对未访问过的顶点调用DFS,如果是连通图,只会执行一次
DFS(G,i);
}
}
}
存储结构为邻接表:
void DFS(GraphAdjList GL, int i){
EdgeNode *p;
visited[i]=TRUE;
printf("%c ",GL->adjList[i].data);
p=GL->adjList[i].firstedge;
while(p!=NULL){
if(!visited[p->adjvex]){
DFS(GL,p->adjvex);
}
p=p->next;
}
}
void DFSTraverse(GraphAdjList GL){
int i;
for(i=0;i<GL->numVertexes;i++){
visited[i]=FALSE; //初始化所有顶点状态为未访问
}
for(i=0;i<GL->numVertexes;i++){
if(!visited[i]){ //对未访问过的顶点调用DFS,如果是连通图,只会执行一次
DFS(G,i);
}
}
}
对于n个顶点e条边的图来说,邻接矩阵由于是二维数组,要查找每个顶点的邻接点需要访问矩阵中的所有元素,因此都需要O(n2)的时间。
而邻接表做存储结构时,找邻接点所需时间取决于顶点和边的数量,所以是O(n+e),显然对于点多边少的稀疏图,邻接表结构使得算法在时间效率上大大提高。
2.3 图的广度优先遍历(非递归)
(邻接矩阵存储)
bool adj[9][9]; //
bool visit[9]; // 标志数组,记录顶点是否被访问
void BFS()
{
// 建立一个queue
queue<int> q;
// 初始化所有的点为false
for (int i=0; i<9; i++)
visit[i] = false;
for (int k=0; k<9; k++)
if (!visit[k])
{
//将起点放入queue
q.push(k);
visit[k] = true;
// 重复,直到queue里面没有东西为止
while (!q.empty())
{
// 从queue中取出一点
int i = q.front(); q.pop();
// 乙、找出跟此点相邻的点,并且尚未被遍历的点
// 依照编号顺序通通放入queue
for (int j=0; j<9; j++)
if (adj[i][j] && !visit[j])
{
q.push(j);
visit[j] = true;
}
}
}
}
时间复杂度:使用的存储结构为邻接矩阵,o(v^2);使用邻接表 adjacency lists, O(V+E)。