数据结构个人简易总结(DFS BFS WPL 最小生成树 哈夫曼编码 有向图 无向图 二叉树 稀疏矩阵 KMP匹配算法 栈和队列 链表)

仅供学习参考,有一些属于模板类算法需要记住,有一些设计应用需要了解大致思路

希望通过这篇文章,读者能了解数据结构大致要学习哪些内容,以便复习参考。

整理作者:黎爬爬(2745907300)

目录

前言

一、链表

1.单链表 

补充方法

2.双链表

3.循环链表与约瑟夫环

4.双向循环链表

二、栈和队列

1.栈

栈的应用--栈递归汉诺塔

栈的应用--后缀表达式

补充

2.队列

链式队列

顺序队列

循环队列

队列应用--银行排队

三、串和稀疏矩阵

KMP匹配算法

LCS动态规划--最长公共子串

稀疏矩阵的快速转置

四、树

二叉树的创建和两种遍历方法(递归和递归工作栈模拟)

二叉树深度和最长叶路径

二叉树左右子树交换

给定数组创建完全二叉树

根据前中序创建二叉树 找公共祖先

根据中后序创建二叉树

根据广义表创建二叉树 按照层次输出

求二叉树最大宽度的两种算法

最优二叉树(哈夫曼树) 哈夫曼编码

哈夫曼树最短带权路径

中序线索二叉树

五、图

图的邻接表创建

图的邻接矩阵

尾插法建图 BFS广度优先搜索

尾插法建图DFS深度优先搜索

最小生成树kruskal算法

最小生成树--prime算法

有向网的最短路径Dijkstra算法

有向无环图的拓扑排序

图的应用--走迷宫DFS

图的应用--走迷宫BFS

图的应用--DFS解决数独问题

六、查找

1.顺序查找

2.二分查找(折半)

3.插值查找

4.斐波拉契查找法

5.二叉排序树

6.平衡二叉树

7.B树

8.哈希查找

七、排序

1.冒泡排序

2.希尔排序

3.归并排序

4.快速排序

5.选择排序

6.堆排序

总结


前言

个人笔记总结

一、链表

1.单链表 

#include<iostream>
using namespace std;
//结构定义 单链表
typedef struct Lnode
{
int data;
struct Lnode*next;
}Lnode,*LinkList;
//头插法创建
void Create1(LinkList &L)
{
int n;//元素个数
cin>>n;
int temp;
LinkList p;
for(int i=1;i<=n;i++)
{
cin>>temp;
p=new Lnode;
p->data=temp;
p->next=L->next;
L->next=p;
}
}
//尾插法创建
void Create2(LinkList L)
{
int n;//元素个数
cin>>n;
int temp;
LinkList p;
while(n)
{
cin>>temp;
p=new Lnode;
p->data=temp;
L->next=p;
L=p;
}
L->next=NULL;
n--;
}

}
//查找第i个元素并且返回指针
LinkList Get(LinkList L,int i)
{
LinkList p=L;
int j=0;
while(p!=NULL&&j<i)
{p=p->next;j++;
}
return p;
}
//增加元素
void Insert(LinkList&L,int i,int x)
{//位置i后插入元素x
LinkList p,s;
p=Get(L,i);
if(!p){cout<<"erro"<<endl;}
else
{
s=new Lnode;
s->data=x;
s->next=p->next;
p->next=s;
}
}
//删减元素
void Delete(LinkList L,int i)
{
LinkList p ,s;
p=Get(L,i-1);
if(!p)cout<<"erro"<<endl;
else if(p->next==NULL)cout<<"erro"<<endl;
else
{
s=p->next;
p->next=s->next;
delete s;
}
}
//改变元素
略
//查找元素
略
//打印链表
void Display(LinkList L)
{
	//单链表的显示
	LinkList p;
    p = L;
	while (p->next)
	{
		cout<<p->next->data<<" ";
        p = p->next;
	}
}
int main()
{
return 0;
}

补充方法

//排序(升序)
void sort(LinkList L)
{
LinkList p,q,temp;
int x;
for(int p=L->next;p->next!=NULL;p=p->next)
{
temp=p;
for(q=temp->next;q!=NULL;q=q->next)
{
if(temp->data>q->data)temp=q;
}
if(t!=p)
{
x=p->data;
p->data=temp->data;
temp->data=x;
}
}
}
//删除重复元素
void deleteRepete(LinkList L)
{
LinkList p =L->next,q;
while(p->next!=NULL)
{
if(p->data==p->next->data)
{
q=p->next;
p->next=q->next;
delete q;
}
else p=p->next;
}
}

2.双链表

#include<iostream>
using namespace std;
//双链表结构定义
typedef struct Lnode
{
	//链式储存结构定义
	int data; //数据域
	struct Lnode* next;//指针域 指后 
	struct Lnode*last;//指针域 指前 
}LNode,*LinkList;//定义节点和头指针类型名
//创建双链表
void create(LinkList s)
{
LinkList p,head;
head=s;
int n;//元素个数
cin>>n;
while(n)//尾插法
{
p=new LNode;
p->data=n;
s->next=p;
p->last=s;
s=p;
cin>>n;
}
}
int main()
{
LinkList L;
L=new LNode;
L->last=NULL;
L->next=NULL;
create(L);
return 0;
}

3.循环链表与约瑟夫环

#include<iostream>
using namespace std;
typedef struct Lnode
{
int data;
struct Lnode*next;
}Lnode,*LinkList;
LinkList Head,Tail;//头指针 尾指针 循环链表 头尾相连
//循环链表初始化
void Init()
{
Head=new Lnode;
Head->next=NULL;
Tail=Head;//尾指针指向头部
}
//创建循环链表
void create()
{
LinkList p;
int m;
cin>>m;//约瑟夫环元素个数
for(int i=1;i<=m;i++)
{
p=new Lnode;
p->data=i;
Tail->next=p;
p->next=Head->next;
Tail=p;
}//尾插法建立
Head=Head->next;
}
//打印操作
void display()
{
LinkList q,p;
p=Head;
q=Tail;
while(p!=q)
{
cout<<p->data<<" ";
p=p->next;
}//输出尾巴之前的元素
cout<<q->data<<endl;//输出尾巴
}
//删除操作
void destory()
{
LinkList p;
while(Head!=Tail)
{
p=Head;
Head=Head->next;
delete p
}//删除尾巴之前的元素
delete Tail;//删除尾巴
}
int main()
{
Init();
create();
display();
desotry();
return 0;
}

4.双向循环链表

#include<iostream>
using namespace std;
#define flag 0
typedef int ElemType;
typedef struct Lnode
{
	//链式储存结构定义
	int data; //数据域
	struct Lnode* next;//指针域 指后 
	struct Lnode*last;//指针域 指前 
}LNode,*LinkList;//定义节点和头指针类型名

void create(LinkList s)
{
	LinkList p,head;
	head=s;
	int n;
	cin >> n;
	while (n) {//双链表的尾插法 
		p = new LNode;
		p->data = n;
		s->next=p;
		p->last=s;
		s=p;
		cin >> n;
	} 
	head->last=s; 
	s->next=head;
	 
}
void Display(LinkList L)
{
	//单链表的显示
	LinkList p; p = L;
	while (p->next!=L)
	{
		cout<<p->next->data<<" "; 
		p = p->next;
	}
}
void sort(LinkList L){

	LinkList p,q,r;
    p=L->next;
    q=p->next;
    r=q->next;
    while(q!=L)
    {
        while(p!=L && p->data>q->data)
        {
            p=p->last;
        }
        q->last->next=r;
        r->last=q->last;
        q->next=p->next;
        q->last=p;
        p->next->last=q;
        p->next=q;
        q=r;
        p=q->last;
        r=r->next;
    }

}
int main( )
{
	
	LinkList L;
	L=new LNode; 
	L->last=NULL;
	L->next=NULL;
	create(L);
	sort(L);
	Display(L);
	
	return 0;
}

二、栈和队列

1.栈

代码如下(示例):

#include<iostream>
using namespace std;
typedef struct Snode
{
	int data;
	struct Snode* next;
	
}Snode,*LinkStack;
void Init(LinkStack& top)
{//链栈初始化 
	top=new Snode;
	top->next = NULL;
}
int isEmpty(LinkStack top)
{
	return (top == NULL);
}
void Push(LinkStack& top, int x)
{//入栈操作 
	LinkStack p;//创建新结点 
	p=new Snode;
	p->data = x;//数据域赋值 
	p->next = top;//记录原栈顶位置 
	top = p;//新结点成为新栈顶 
}
int pop(LinkStack& top, int &e)
{//出栈操作 
	LinkStack p;
	if (top == NULL) return 0;//判断是否空栈 
	p = top;//新结点指向栈顶 
	e = p->data;//栈顶元素通过参数返回 
	top = p->next;//原栈顶结点后的结点直接继承为新的栈顶结点 
	delete p;//释放原栈顶结点空间 
	return 1;
}
int Top(LinkStack top, int &e)
{//读取栈顶元素 
	if (top == NULL)return 0;
	e = top->data;
	return 1;
}
void display(LinkStack top)
{//按照“后进先出”顺序读取栈元素 
	LinkStack p = top;
	while (p->next != NULL)
	{
		cout<<p->data<<" "; p = p->next;
	}
	cout<<endl;
}
void Clear(LinkStack &top)
{
	LinkStack p;
	while(1)
	{
	p = top;//新结点指向栈顶 
	top = p->next;//原栈顶结点后的结点直接继承为新的栈顶结点 
	delete p;//释放原栈顶结点空间
	if(top->next==NULL)break;
	}
} 

栈的应用--栈递归汉诺塔

#include<iostream>
using namespace std;
#define SIZE 100;
//汉诺塔结构定义
typedef struct
{
int n;
char A;
char B;
char C;
}HannioPara;
//栈的结构定义
typedef struct stack
{
int top;
int maxtop;
HannioPara *data;
}stack,*Stack;
//汉诺塔栈初始化
Stack Init(int size)  //初始化
{
    Stack S = new stack;
    S->data = new HanioPara[size];
    S->maxtop = size;
    S->top = -1;
    return S;
}
//判断栈空
int isEmpty(Stack S)  
{
    return S->top<0;
}
//判断栈满 
int isFull(Stack S)  //栈满
{
    return S->top >= S->maxtop;    
}
//入栈操作
int Push(Stack S,ElemList i)  
{
    if(isFull(S))return 0;
    S->data[++S->top]=i;
    return 1;
}
//出栈操作
HanioPara Pop(Stack S) 
{
    if(isEmpty)return 0;
    return S->data[--S->top];
}
//打印
void display(Stack S) 
{
    while (!(isEmpty(S)))
    {
        printf("%d%c%c%C\n", S->data[S->top--].n,S->data[S->top--].A,S->data[S->top--].B,S->data[S->top--].C);
    }    
}
//打印移动过程
void Printmove(char a,char c) 
{
    printf("第%4d次\t\t%c----->%c\n",flag,a,c);
    flag++;
}
//汉诺塔创建函数
int hannio(int n,char a,char b,char c) //模拟递归实现
{
    Stack S = StackInit(200);
    HannioPara tem,tem1,tem2;
    tem.n = n;
    tem.A = a;
    tem.B = b;
    tem.C = c;
    Push(S,tem);//最外层函数最先入栈
    while(S->top > -1) //栈不为空,出栈
    {
        tem1 = StackTop(S);//栈顶元素
        Pop(S); //出栈
        if (tem1.n > 1)
        {
            int n = tem1.n;
            tem2.n = n-1;
            tem2.A = tem1.B;
            tem2.B = tem1.A;
            tem2.C = tem1.C;
            Push(S,tem2);//b,a,c 压栈
            tem2.n = 1;
            tem2.A = tem1.A;
            tem2.C = tem1.C;
            Push(S,tem2);//a,c 压栈
            tem2.n = n-1;
            tem2.A = tem1.A;
            tem2.B = tem1.C;
            tem2.C = tem1.B;
            Push(S,tem2);//a,c,b 压栈
        }
        else printmove(tem1.A,tem1.C);
    }
}

int main()
{
    hannio(3,'A','B','C');  //调用
    return 0;
}

栈的应用--后缀表达式

/*洛谷题目 后缀表达式
所谓后缀表达式是指这样的一个表达式:式中不再引用括号,运算符号放在两个运算对象之后,所有计算按运算符号出现的顺序,严格地由左而右新进行(不用考虑运算符的优先级)。

如:3*(5–2)+7对应的后缀表达式为:3.5.2.-*7.+@。’@’为表达式的结束符号。‘.’为操作数的结束符号。
【输入样例】
3.5.2.-*7.+@
【输出样例】
16
*/
#include<iostream>
#include<stack>
using namespace std;
//数学基础运算函数  运算符号c  运算数字1:a 运算数字2:b
int Math(int a,char c,int b)
{
	if (c == '+')return a + b;
	else if (c == '-')return a - b;
	else if (c == '*')return a * b;
	else return a / b;
}
int main()
{
stack<int>ds;//创建工作栈
char ch;
int a,b,temp;
cin>>ch;
while(cj!='@')//@为结束符号
{
if(ch>='0'&&ch<='9')
{//输入的是数字字符
cin.pushback(ch)//将输入的数字字符退回
cin>>temp;//将退回的数字字符重新输入 赋值给temp
ds.push(temp);//遇到数字无条件入栈
cin>>ch;//重新输入字符
}
else if(ch=='.')cin>>ch;
else
{//遇到非数字字符也就是运算符
a=ds.top();ds.pop();//弹出两个栈顶数字
b=ds.top();ds.pop();
ds.push(Math(b,ch,a));//将运算的结果压入数字栈
cin>>ch;//重新输入
}
}
cout<<ds.top();//输出最终结果
return 0;
}

补充

//栈在c++中可以直接调用
#include<stack>

//1.创建一个栈
stack<int>S1;
stack<char>s2;

//2.入栈操作
S1.push(1);
S1.push(2);
S2.push(a);
S2.push(b);

//3.出栈操作
s1.pop()//弹出栈顶元素

//4.获取栈顶元素
char a;
a=s2.top();

//是否空 
printf(s1.empty());
//6.清空
s1.clear();
s2.clear();

2.队列

链式队列

#include<iostream>
using namespace std;
//队列结构定义
struct Node
{
Node *next;
int data;
};
struct Queue
{
Node*front;//队首
Node*rear;//队尾
}
//初始化
void Init(Queue&queue)
{
Node*p;
p=new Node;
p->next=NULL;//队首
queue.front=p;
queue.rear=p;
}
//入队操作  遵循原则先进先出 从队尾进 从队首出
void Enqueue(Queue&queue,int x)
{
Node*p;
p=new Node;
p->next=NULL;
p->data=x;
queue.rear->next=p;
queue.rear=p;
}
//出队操作  先进先出  尾进首出
void Dequeue(Queue&queue,int &x)
{
Node*p;
p=queue.front->next;
x=p->data;
queue.front->next=p->next;
if(p==queue.rear)queue.rear=queue.front;//如果仅剩下一个元素 队首等于队尾
delete p;
}
//判断是否为空
int isempty(Queue&queue)
{
if(queue.front==queue.rear)return 1;//队首等于对尾 为空返回真
else return 0;
}
//清空队列
void Distory(Queue &queue)
{
while(queue.front)
{
queue.rear=queue.front;
delete queue.front;
queue.front=queue.rear;
}
}

顺序队列

 #include<iostream>
#define MAXSIZE 10
using namespace std;
//顺序存储结构定义
typedef struct
{
int queue[MAXSIZE];
int rear;
int front;
}Queue;
//队列初始化
void Init(Queue *Q)
{
Q->front=Q->rear=0;
}
//判断是否为空队列
bool isempty(Queue*Q)
{
return Q->front==Q->rear;
}
//判断队列是否已满
bool isfull(Queue*Q)
{
return (Q->rear+1)%MAXSIZE==Q->front;
}
//获取队列长度
int Getlength(Queue*Q)
{
int length=(Q->rear-Q->front+MAXSIZE)%MAXSIZE;
return length;
}
//入队操作   先进先出  尾进首出
void EnQueue(Queue*Q,int x)
{
if(isfull(Q))cout<<"队列已满"<<endl;
else
{
Q->queue[Q->rear]=x;//入队值赋给队尾指针指向的地方
Q->rear=(Q->rear+1)%MAXSIZE//队尾指针后移
}
}
void DeQueue(Queue*Q,int &x)
{
if(isempty(Q))cout<<"已空"<<endl;
else
{
x=Q->queue[Q->front];
Q->front=(Q->front+1)%MAXSIZE;
}
}
//清空队列
void clear(Queue*Q)
{
Q->front=Q->rear=0;
}
//打印队列
void display(Queue*Q)
{
if(isempty(Q))COUT<<"空队列"<<endl;
else
{
for(int i=Q->front;i%MAXSZIE<Q->rear;i++)
{
if (i == Q->rear - 1)
		{
			//遍历到尾元素
			cout << Q->queue[i] << endl;
		}
		else
		{
			cout << Q->queue[i] << " ";
		}
}
}
}

循环队列


循环队列入队, 队尾循环后移: SQ->rear =(SQ->rear+1)%Maxsize;
循环队列出队, 队首循环后移: SQ->front =(SQ->front+1)%Maxsize;
队空: SQ.front=SQ.rear; // SQ.rear 和 SQ.front 指向同一个位置
队满: (SQ.rear+1) %Maxsize=SQ.front; // SQ.rear 向后移一位正好是 SQ.front
计算元素个数:
可以分两种情况判断:

如果 SQ.rear>= SQ.front:元素个数为 SQ.rear-SQ.front;
如果 SQ.rear<SQ.front:元素个数为 SQ.rear-SQ.front+ Maxsize;
采用取模的方法把两种情况统一为:(SQ.rear-SQ.front+Maxsize)% Maxsize

 #include<iostream>
#define MAXSIZE 10
using namespace std;
//循环队列结构定义
typedef struct
{
int queue[MAXSIZE];
int rear;
int front;
int count;//计数器
}Queue;
//初始化
void Init(Queue* Q) 
{
	Q->front = Q->rear = Q->count = 0;
}
//销毁顺序队列
void clear(Queue* Q)
{
		Q->front = Q->rear = Q->count = 0;
}
//判断顺序队列是否为空
int IsEmpty(Queue* Q) {
	return (Q->count == 0);
}
//判断顺序队列是否已满
int iSFull(Queue* Q) {
	return (Q->count == MAXSIZE);
}
//求得顺序队列的长度
int GetLength(Queue* Q) {
	return Q->count;
}
//获得队首元素
void GetHead(Queue*Q)
{
if(IsEmpty(Q))cout<<"空队列"<<endl;
else
{
cout<<Q->queue[Q->front]<<endl;
}
}
//取得顺序队列的的队尾
void GetRear(Queue* Q)
{
if (IsEmpty(Q)) 
{
	if(IsEmpty(Q))cout<<"空队列"<<endl;	
	else
{
		cout<<Q->queue[(Q->rear - 1 + MAXSIZE) % MAXSIZE]<<endl;
}
}
//入队操作
void Enqueu(Queue*Q,int x)
{
if(iSFull(0))cout<<"已满"<<endl;
else
{
Q->queue[Q->rear]=x;
Q->rear=(Q->rear+1)%MAXSIZE;
Q->count++;
}
}
//出队操作
void Dequeue(Queue *Q)
{
if(IsEmpty(Q))cout<<"空队列"<<endl;
else
{
Q->front=(Q->front+1)%MAXSZIE;
Q->count--;
}
}
//打印顺序队列
void display(Queue* Q) {
	int i = 0;
	int j = Q->front;
	if (IsEmpty(Q)) {
	cout<<"空队列"<<endl;
	}
	else {
		while (i < Q->count) {
			cout<<Q->queue[j]<<" ";
			j = (j + 1) % MAXSIZE;
			i++;
		}
		cout<<endl;
	}
}

队列应用--银行排队

 

三、串和稀疏矩阵

KMP匹配算法


前缀:包含首位字符但不包含末位字符的子串
前缀:包含末位字符但不包含首位字符的子串
next数组的定义:当主串与模式串的某一位字符不匹配时,模式串要回退的位置
next[j]:其值=第j位前面j-1位字符组成的子串的前后缀重合字符数+1
[输入样例1]
ludongli
dongli    
[输出样例1]2
[输入样例2]
ababcabcacbab
abc
[输出样例2]
3
[输入样例3]
abcdefghigk
higke
[输出样例2]
no
 


#include<iostream>
using namespace std;
#define MAXSIZE 100
//串的结构定义
typedef struct 
{
char data[MAXSIZE];
int length;
}String;
int Strlen(String s)
{
return s.length;
}
//求子串的next数组函数
void GetNext(char ch[],int length,int *next)
{//length是子串ch的长度 next为数组这里用到址传递
next[1]=0;//0位置不存储数据
int i=1,j=0;
while(i<=length)
{
if(j==0||ch[i]==ch[j])next[++i]=++j;
else j=next[j];
}
}
//KMP快速匹配算法 母串和子串 返回匹配的字符串在母串中第一个字符出现的索引位置
int KMP(String Mother,String Son)
{
int next[MAXSIZE];
int i=1,j=1;
int len=Strlen(Son);
GetNext(Son.data,len,next);
/*	cout<<"子串的next数组为:";
	for(int i=1;i<=len;i++)
	{
		cout<<next[i]<<" ";
	 } 
	 cout<<endl;
*/
while(i<=Mother.length&&j<=Son.length)
{
if(j==0||Mother.data[i]==Son.data[j])
{//母串与子串当前字符匹配 后移
i++;j++;
}
else
{
j=next[j];
}
}
if(j>Son.length) return i-Son.length;//返回匹配的子串在母串中第一个字符出现的索引位置
else return -1;//匹配失败 没有找到
}
//串的创建
void Strcre(String *s)
{
char temp;
int i=1;
s->length=0;
while(1)
{
t=getchar();
if(t!='\n')
{
s->data[i]=t;
i++;
s->length++;
}
else break;
}
}
int main()
{
String Mother,Son;
Strcre(&Mother);
Strcre(&Son);
int flag=KMP(Mother,Son);
if(flag!=-1)
{
cout<<"匹配位置:"<<flag<<endl;
}
else
{
cout<<"no"<<endl;
}
return 0;

}

LCS动态规划--最长公共子串

【问题描述】
给定两个字符串,求出它们两个最长的相同子字符串的长度。
最长公共子串(Longest Common Substring)是一个非常经典的面试题目,在实际的程序中也有很高的实用价值。同学们不应该单单只是写出该问题的基本解决代码而已,关键还是享受把算法一步步的优化,让时间和空间复杂度一步步的减少的惊喜。
【输入形式】
两个字符串
最长的公共子串,若没有则输出“no”
【输出形式】
【样例输入】
acbcbcef
abcbced
【样例输出】
abcbce
【解释说明】
1.设计状态
f[i][j]表示序列s1 ,s2的最长公共子序列的长度
2.状态转移方程
f[I][j]=0,i=0或者j=0
f[i][j]=1+f[i-1][j-1], xi=yi
f[i][j]=max{f[i-1][j],f[i][j-1]}  xi!=yi
时间复杂度O(mn)+O(m+n)


#include<iostream>
#include<string.h>
#define MAX 1010
using namespace std;
char s1[MAX],s2[MAX];//两个字符串
int f[MAX][MAX],n,m;//动态规划矩阵 n m记录两个串长度

//LCS函数部分
void LCS(int i,int j)
{
//设置退出条件|终止条件
if(i==0;j==0)return;
if(s1[i-1]==s2[j-1])
{
LCS(i-1,j-1);
cout<<s1[i-1];
}
else if(f[i][j-1]>f[i-1][j]) LCS(i,j-1);
else LCS(i-1,j);
}
int main()
{
	
	cin>>s1>>s2;
	n = strlen(s1);
	m = strlen(s2);
	for (int i = 1; i <= n; i++)
	{//填充状态表格
		for (int j = 1; j <= m; j++)
		{
			if (s1[i - 1] == s2[j - 1])
			{//xi=yi  
				f[i][j] = 1 + f[i - 1][j - 1];
			}
			else
			{//xi!=yi
				f[i][j] = max(f[i - 1][j], f[i][j - 1]);
			}
		}
	}
	LCS(n, m);
	return 0;
}

稀疏矩阵的快速转置

稀疏矩阵基本操作
1.什么是稀疏矩阵?非零元素组成的矩阵,只存储非零元素
2.用三元组法表示稀疏矩阵   i,j,data  行 列 数据
3.快速转置
输入样例:
3 3
0 0 0
1 2 3
0 0 0
输出样例:
行 列 元素值
2 1 1
2 2 2
2 3 3
快速转置后:
1 2 1
2 2 2
3 2 3

#include<iostream>
#define MAX 100
using namespace std;
typedef int DataType;
//三元组结构定义
typedef struct
{
int row ,col;//行列坐标
DataType val;//数据
}SPNode;
//稀疏矩阵结构定义
typedef struct
{
int rows,cols;//行列数
int n;//非零元素个数
SPNode data[MAX];
}SPMatrix;
//创建稀疏矩阵  输入二维矩阵  变成 稀疏矩阵存储方式 节省内存空间
void create(SPMatrix& s, DataType a[MAX][MAX])
{
	int p = 1,i,j;
	for (i = 1; i <= s.rows; i++)
	{

		for (j = 1; j <= s.cols; j++)
		{
			if (a[i][j] != 0)
			{
				s.data[p].row = i, s.data[p].col = j;
				s.data[p].val = a[i][j]; p++;
			}
		}
	}
	s.n = p-1 ;
}
//输出稀疏矩阵的三元组表示形式
void display(SPMatrix s)
{
	cout << "行 列 元素值" << endl;
	for (int i = 1; i <= s.n; i++)
	{
		cout << s.data[i].row << " " << s.data[i].col << " " << s.data[i].val << endl;
	}
}
//快速转置算法
void FastTran(SPMatrix t,SPMatrix&t2)
{
int q, number[MAX], position[MAX];
	//number[]统计某个列元素出现的个数
	// position[]统计某个列元素第一次出现的位置
	
//两稀疏矩阵行列交换 
    t2.cols = t.rows;
	t2.rows = t.cols;
	t2.n = t.n;
//扫描列 所有列元素可能的值的个数重置为0 
	for (int j = 1; j <= t.cols; j++)
	{
		number[j] = 0;
	}
//列元素出现个数统计 
	for (int j = 1; j <= t.n; j++)
	{
		number[t.data[j].col]++;
	}
position[1] = 1; int j;
    for (j = 2; j <= t.cols; j++)
	{
		position[j] = position[j - 1] + number[j - 1];
	}
for (int p = 1; p <= t.n; p++)
	{
		j = t.data[p].col;
		q = position[j];//取首地址
		t2.data[q].col = t.data[q].row;
		t2.data[q].row = t.data[q].col;
		t2.data[q].val = t.data[q].val;
		position[j]++;
	}
}
int main()
{
	SPMatrix s,s2;
	DataType a[MAX][MAX];
	int i, j;
	cout << "输入行 列" << endl;
	cin >> s.rows >> s.cols;
	for (i = 1; i <= s.rows; i++)
	{
		for (j = 1; j <= s.cols; j++)
		{
			cin >> a[i][j];
		}
	}
	create(s, a);
	display(s);
	FastTran(s, s2);
	cout << "快速转置后:" << endl;
	display (s2);
}

四、树

二叉树的创建和两种遍历方法(递归和递归工作栈模拟)

【问题描述】
 给出一个按照先序遍历得出的字符串,'#' 代表空的子节点,大写字母代表节点内容。请通过这个字符串建立二叉树,
 并分别采用“递归”和“非递归”的先序、中序、后序遍历的算法分别输出每一个非空节点。
【输入形式】
 输入只有一行,包含一个字符串S,用来建立二叉树。保证S为合法的二叉树先序遍历字符串,
 节点内容只有大写字母,且S的长度不超过100。
【输出形式】
 共有6行,每一行包含一串字符,表示分别按递归和非递归的先序、中序、后序遍历得出的节点内容,
 每个字母后输出一个空格。请注意行尾输出换行。
【样例输入】
ABC##DE#G##F###
【样例输出】
A B C D E G F
C B E G D F A
C G E F D B A
A B C D E G F
C B E G D F A
C G E F D B A

#include<cstdio>
#include<stack>
#include<iostream>
using namespace std;
//二叉树结构定义
typedef struct Node
{
    char date;
    struct Node* lchild, * rchild;
} Node, * Tree;
int d = 0, ma = 0, num = 0;
//创建二叉树
void build(Tree&T)
{
char ch;
cin>>ch;
if(ch==='#')T=NULL;
else if(ch=="\n")return ;
else
{
T=new Node;
T->data=ch;
build(T->lchild);
build(T->rchild);
}
}
//递归遍历
void DLR(Tree T)
{
    if (T)
    {
        cout << T->date << " ";
        DLR(T->lchild);

        DLR(T->rchild);
    }
}
void LDR(Tree T)
{
    if (T)
    {
        LDR(T->lchild);
        cout << T->date << " ";
        LDR(T->rchild);
    }
}

void LRD(Tree T)
{
    if (T)
    {
        LRD(T->lchild);
        LRD(T->rchild);
        cout << T->date << " ";
        if (ma < d)
            ma = d;//防止同一层多次相加
    }
}
//非递归算法
//先序遍历
/*1. 建立一指针root,令其指向二叉树的根节点
2. 如果栈为空则返回,不为空则循根节点->左子树的顺序一路向左访问节点并入栈
3. 当到达最左边节点时,栈中最上方元素出栈
4. 如果出栈元素有右子树,则令root指向它,并重复2、3步
5. 如果出栈元素无右子树,则继续出栈元素并重复4、 5步
*/
void preorder(Tree root)
{
if(!root)return ;
stack<Tree>s;//递归工作栈模拟
while(root)
{
cout<<root->data<<" ";
if(root->lchile)
{//左子树存在 当前根入栈 待输出
s.push(root);
//更新根结点
root=root->lchild;
}
else if(root->rchild)
{//右子树存在 更新根结点
root=root->rchild;
}
else
{
while (!root->rchild && !s.empty())//右子树非空 或 栈非空
            {//先序遍历: 根 左 右
                root = s.top();//回溯到之前的根结点
                s.pop();//取出栈元素
            }
            root = root->rchild;//更新根结点
}
}
}
/*
1.设立指针root,并指向二叉树的根节点
2.找到root最左边的节点,并将沿路节点入栈
3.元素出栈并令root指向它,访问之
4.如果root有右子树,令root指向它,算法回到2
5.如果root没有右子树,重复3、4、5*/
void inorder(Tree root)
{//中序遍历 左 根 右
    if (!root)return;
    stack<Tree>s;
    while (root)
    {
        while (root->lchild)//找到最左边的结点
        {
            s.push(root);
            root = root->lchild;
        }
        cout << root->date << " ";//访问
        while (!root->rchild && !s.empty())
        {
            root = s.top();
            s.pop();
            cout << root->date << " ";
        }
        root = root->rchild;
    }
}
/*
1.设立指针root,指向二叉树根节点
2.一路向左走,走到最左边节点,并将沿途节点压人栈中,且标记为true
3.访问栈顶节点
4.如果栈顶节点标记为true,将其标记为false,令root指向其右子树
5.如果栈顶节点标记为false,将其出栈并访问之
注意后序遍历需要有一个标记的东东看看是否被访问过*/
struct TreeNodeWithMark
{
    Tree node;
    bool isFirst;
    TreeNodeWithMark(Tree p):node(p),isFirst(true){}//初始化设置可访问
};
void postorder(Tree root)
{//后序遍历 左 右 根
        if (!root)
        {
            return;
        }
        stack<TreeNodeWithMark*> s;
        s.push(new TreeNodeWithMark(root));
        TreeNodeWithMark* curr;
        while (!s.empty())
        {
            curr = s.top();
            root = curr->node;
            s.pop();
            if (curr->isFirst)
            {
                curr->isFirst = false;
                s.push(curr);
                if (root->rchild)
                {
                    s.push(new TreeNodeWithMark(root->rchild));
                }

                if (root->lchild)
                {
                    s.push(new TreeNodeWithMark(root->lchild));
                }
            }
            else
            {
                cout << root->date << " ";
            }
        }
    }


int main()
{
    Tree T;
    build(T);
    DLR(T); cout << endl;
    LDR(T); cout << endl;
    LRD(T); cout << endl;

    preorder(T); cout << endl;
    inorder(T); cout << endl;
    postorder(T); cout << endl;
    return 0;
}

二叉树深度和最长叶路径


【问题描述】考研真题:求二叉树的深度及二叉树中最远两个结点的距离。
【输入形式】拓展的前序遍历序列
【输出形式】深度和距离
【样例输入】
AB#C##DE#G#H##F##
【样例输出】
5 6
[解析]
         A
     B        D
    # C    E    F
     # #  # G  # #
           # H
            # #
求最远两个结点的距离,实际上就是两个叶子的距离 
 首先明确距离最大的两个结点出现位置:1,同时在根结点的左子树中;2,同时在根结点的右子树中;3,左右子树中各有一个结点。
  将左子树两个结点最长距离,右结点两个结点最长距离,左、右子树高度之和比较,取得三者中的最大值,即为这棵二叉树中两个结点距离的最大值。
1 全局变量flag保存结果
2 对T求最大深度
求节点最大深度的函数:
1 求左子树最大深度leftHeight
2 求右子树最大深度rightHeight
3 计算当前节点的直径 temp = left+right,判断temp是否大于flag,维护全局变量flag
4 返回当前节点深度 1 + max(leftHeight, rightHeight)
 

#include<iostream>
using namespace std;
//二叉树结构定义
typedef struct Node
{
    char date;
    struct Node* lchild, * rchild;
} Node, * Tree;
int d = 0, ma = 0, num = 0;
//拓展序列创建二叉树
void build(Tree& T)
{
    char ch;
    cin >> ch;
    if (ch == '#')
        T = NULL;
    else if (ch == '\n')
    {
        return;
    }
    else
    {
        T = new Node;// 让指针实体化 new返回的是jied的指针
        T->date = ch;
        build(T->lchild);
        build(T->rchild);
    }
}
//求取深度
int Depth(Tree T)
{
if(T==NULL)return 0;//递归退出条件
int leftDepth=Depth(T->lchild);
int rightDepth=Depth(T->rchild);
//三目运算符 如果左深度大于右深度 左+1 否则 右+1
return leftDepth>rightDepth?leftDepth+1:rightDepth+1;
}
int flag;
//求取二叉树最大宽度
int findMaxLen(Tree T,int &flag)
{
if(T==NULL)return 0;
int lMaxlen=findMaxLen(T->lchild,flag);
int rMaxlen=findMaxLen(T->rchild,flag);
flag=max(flag,lMaxlen+rMaxlen);
return max(lMaxlen,rMaxlen)+1;
}
int main()
{
    Tree T;
    build(T);
    int deep = Depth(T);
    cout <<deep;
    flag = 0;
   findMaxLen(T,flag);
   cout << " "<<flag;
    return 0;
}

二叉树左右子树交换

【问题描述】二叉树按照二叉链表的方式存储。
编写程序,计算二叉树中叶子结点的数目并输出;
编写程序,将二叉树的每个结点的左、右子树进行交换,
请注意不是只交换结点的data值,而是左、右孩子指针指向的交换,最后输出交换后的二叉树的后序遍历序列。
【输入形式】二叉树的前序遍历序列,空指针的位置输入字符#
【输出形式】叶子结点的数目;左右子树交换后,后序遍历的序列,空子树的位置输出字符#
【样例输入】
ABE##F##CG###
【样例输出】
3
###GC##F##EBA
[解析]叶子特点就是 左右孩子都是空
所谓左右子树交换倒不如说是按照 根右左的方式遍历得到 AC#G##BF##E## 然后将这个序列按照先序序列创建一个“交换后”的树 ,然后按照后序顺序输出 遇到空输出@
 

#include<iostream>
using namespace std;
//二叉树结构定义
typedef struct Node
{
    char date;
    struct Node* lchild, * rchild;
} Node, * Tree;
//二叉树的创建
void build(Tree& T) {
    char ch;
    cin >> ch;
    if (ch == '#')T = NULL;
    else if (ch == '\n')return;
    else
    {
        T = new Node;
        T->date = ch;
        build(T->lchild);
        build(T->rchild);
    }
}
int n;
n=0;
//输出叶子总数
void findLeaf(Tree root)
{
    if (root == NULL)return;
    if (root->lchild != NULL)findLeaf(root->lchild);
    if (root->rchild != NULL)findLeaf(root->rchild);
    if (root->lchild == NULL && root->rchild == NULL)n++;
    return;
}

void LRD(Tree T)
{
    if (T)
    {
        LRD(T->lchild);
        LRD(T->rchild);
        cout << T->date;
    }
    else
    {
        cout << "#";
    }
}
char a[100];
char* p = a;
//将二叉树按照 根右左 的序列输出赋值为数组a[]
void DRL(Tree root)
{
    if (root)
    {
        *p = root->date;
        p++;
        DRL(root->rchild);
        DRL(root->lchild);
    }
    else
    {//遇到空赋值#
        *p = '#';
        p++;
    }
}
string b(a);
int index = 0;
//根据数组a交换后的先序拓展序列创建二叉树
void Build(Tree& T) {
    char ch;
    ch = b[index]; index++;
    if (ch == '#')T = NULL;
    else if (ch == 0)return;
    else
    {
        T = new Node;
        T->date = ch;
        Build(T->lchild);
        Build(T->rchild);
    }
}
int main ()
{
    Tree T;
    build(T); n = 0;
    findLeaf(T);
    cout << n << endl;

    DRL(T);//得到a[]=AC#G##BF##E## 赋值给string b
    b = a;
    //cout << b << endl;
    Tree t;
    Build(t);
    LRD(t);
    return 0;
}

给定数组创建完全二叉树

【问题描述】课后作业习题25:n个结点的完全二叉树顺序存储在一维数组a中,设计一个算法,实现对此二叉树的先序遍历。
【输入形式】一维数组a,以#结束输入,请接收后存储在一维数组a中
【输出形式】先序遍历序列
【样例输入】ABCDEF#
【样例输出】ABDECF
【解析】
ABCDEF 在数组中的位置对应于:(实际上位置1对应a[1-1] 位置6对应a[6-1]这个要注意一下)
123456 1根 2i是左子树 3i是右子树   一共6 (假设有n)
step1:build(T,1) 初始化 左右子树都设置空 
step2:当前为编号1的结点 赋值为A   T->data=a[1-1]=a[0]='A' 
step3:然后开始判断要不要建立左子树 和 右子树
判断条件:2*i左孩子编号<=6(总结点数) 就递归创建左子树 
step:4:   2*i+1右孩子编号<=6 表示 右孩子存在 就递归创建右子树

#include<iostream>
using namespace std;
char a[100];
int n;
//二叉树结构定义
typedef struct Node
{
    char date;
    struct Node* lchild, * rchild;
} Node, * Tree;
//根据数组创建二叉树
Tree build(Tree& T,int i) {
    if (i > n)return NULL;
    else
    {
        T = new Node;
        T->lchild = NULL;//初始化
        T->rchild = NULL;
        T->date = a[i-1];//给当前根赋值 注意对应到数组时要减1 因为数组索引是从0开始
        if (2 * i <= n)
        {
            build(T->lchild,2*i);
        }
        if (2 * i + 1 <= n)
        {
            build(T->rchild,2*i+1);
        }
    }
    return T;
}
void DLR(Tree T)
{
    if (T!=NULL)
    {
        cout << T->date;
        DLR(T->lchild);
        DLR(T->rchild);
    }
}
int main ()
{
    Tree T;
    cin>> a;
    for (int i = 0; a[i]; i++)
    {
        n++;
    }
    n = n - 1;//将结束符不计算在里面 也就是ABCDEF一共6个结点
    Tree t=build(T,1);
    DLR(t);
    return 0;
}

根据前中序创建二叉树 找公共祖先

【问题描述】假设二叉树采用二叉链表方式存储,root指向根结点,p所指结点和q所指结点为二叉树中的两个不同结点,
且互不成为根到该结点的路径上的点,编程求解距离它们最近的共同祖先。
【输入形式】二叉树的前序和中序遍历序列,用以创建该二叉树的链式存储结构;以及二叉树的两个结点数据 x 和 y
【输出形式】结点数据值为 x 和结点数据值为 y 的最近的共同祖先,若没有共同祖先则输出NULL,请注意一个结点本身不能成为另一个结点的共同祖先。
【样例输入】
GABDCEF
BDAEFCG                  
DF
abcdefgh
bcafegdh 
【样例输出】
A
【基本思路】首先要解决的是根据前中序列创建二叉树
根据先序遍历的特点,可知preorder[0]一定是整棵二叉树的根节点,那么,如果已确定根节点值在中序遍历序列inorder中的下标是i,就一定有
inorder[0 : i - 1]这些值属于根节点的左子树
inorder[i + 1, n - 1]这些值属于根节点的右子树
因为中序遍历是从最左边开始遍历,所以当遍历到根节点inorder[i]时,之前遍历到的节点一定都属于左子树
那么现在的目的就是如何确定左右子树的根节点,由先序遍历可知
preorder[0 + 1]一定是左子树的根节点
preorder[0 + 1 + (i - 0)]一定是右子树的根节点
因为先序遍历遍历到的节点顺序是从根节点到最左边,再到右边,那么既然已经直到左子树上有多少节点了(i - 0个),那么就可以确定右子树的根节点
直接递归即可

#include<vector>
#include<iostream>
using namespace std;
//二叉树的结构定义
typedef struct Node
{
    char date;
    struct Node* lchild, * rchild;
} Node, * Tree;
//根据先序中序创建二叉树
Tree build(int prestart, int instart, int inend, vector<char>preorder, vector<char>inorder)
{
    if (prestart >=preorder.size() || instart > inend)return NULL;
    Tree root = new Node;
    root->date = preorder[prestart];
    int inIndex = 0;
    for (int i = instart; i <= inend; i++)
    {//找到根节点
        if (inorder[i] == preorder[prestart])
        {
            inIndex = i; break;
        }
    }//根节点索引为inIndex
    //递归调用 创建这个根节点的左子树和柚子树
    root->lchild = build(prestart + 1, instart, inIndex - 1, preorder, inorder);
    root->rchild = build(prestart + 1 + inIndex - instart, inIndex + 1, inend, preorder, inorder);
return root;
}
void DLR(Tree T)
{
    if (T)
    {
        cout << T->date ;
        DLR(T->lchild);
        DLR(T->rchild);
    }
}
void LRD(Tree T)
{
    if (T)
    {
        LRD(T->lchild);
        LRD(T->rchild);
        cout << T->date;
    }
}
//寻找公共祖先
Tree search(Tree T, Tree node1, Tree node2)
{
    if (T == NULL)return NULL;
    if (T->date == node1->date || T->date == node2->date)return T;
    Tree left = search(T->lchild, node1, node2);
    Tree right = search(T->rchild, node1, node2);
    if (left && right)return T;
    if (left)return left;
    if (right)return right;
    return NULL;
}
Tree Getparent(Tree T, Tree node1, Tree node2)
{
    Tree p = search(T, node1, node2);
    return p;
}
int main ()
{
    char a[100], b[100];
    cin >> a >> b;

    vector<char>inorder;
    vector<char>preorder;
    for (int i = 0; a[i]; i++)
    {
        preorder.push_back(a[i]);
        inorder.push_back(b[i]);
    }
    //cout << inorder.size() << preorder.size();
    Tree T;
    T = build(0, 0, inorder.size()-1,preorder,inorder);
    LRD(T); cout << endl;
    return 0;
}

根据中后序创建二叉树

【问题描述】
 根据一棵二叉树的中序遍历序列和后序遍历序列,
求这棵树的前序遍历序列。
【输入形式】 
一棵树的中序遍历序列和该树后序遍历序列。
输入序列中仅含有小写字母,且没有重复的字母
【输出形式】 一棵树的前序遍历序列
【样例输入】
dbeafcg
debfgca
【样例输出】
abdecfg
 

#include<vector>
#include<iostream>
using namespace std;
typedef struct Node
{
    char date;
    struct Node* lchild, * rchild;
} Node, * Tree;
Tree build(vector<char>inordeer, vector<char>postorder, int instart, int inend,int postart, int poend)
{//inorder中序序列数组 postorder后序序列数组 instart在中序数组中需要检索的开始位置 postart后序数组序号检索的开始位置,poend终止位置 对应根的位置
    if (instart > inend)return NULL;
    else
    {
        Tree root = new Node;
        root->date = postorder[poend];//找到根位置并且赋值
        int pos;//记录根的索引
        //在中序中找到根所在的位置
        for (pos = instart; pos <= inend; pos++)
        {
            if (inordeer[pos] == root->date)break;
        }
    //递归查找左子树 和右子树 
           root->lchild = build(inordeer, postorder, instart, pos - 1, postart, postart + pos - instart - 1);
           root->rchild = build(inordeer, postorder, pos + 1, inend, pos - instart + postart, poend - 1);
           return root;
    }   
}
void DLR(Tree T)
{
    
    if (T)
    {
        cout << T->date ;
        DLR(T->lchild);
        DLR(T->rchild);
    }
}
int main ()
{
    char a[100], b[100];
    cin >> a >> b;
    vector<char>inorder;
    vector<char>postorder;
    for (int i = 0; a[i]; i++)
    {
        inorder.push_back(a[i]);
        postorder.push_back(b[i]);
    }
    /*cout << inorder.size() << postorder.size();*/
    Tree T;
    T = build(inorder, postorder, 0, inorder.size()-1, 0, postorder.size()-1);
    DLR(T); cout << endl;

    return 0;
}

根据广义表创建二叉树 按照层次输出

问题描述】 考研真题:给定一颗二叉树,要求从下至上按层遍历二叉树,每层的访问顺序是从左到右,每一层单独输出一行。
【输入形式】 广义表表示的二叉树,结点元素类型为整型,且都大于0,例如:1( 2( 3 ( 4, 5 ) ), 6( 7, 8( 9, 10 ) ) )
【输出形式】 从下至上,打印每一层的结点元素值,元素间以空格隔开。每层的访问顺序是从左到右,每一层单独输出一行。
【样例输入】 
1(2(3(4,5)),6(7,8(9,10)))
,字符串内没有空格
【样例输出】
 4 5 9 10
 3 7 8
 2 6
 1
【解析】
由于是倒着从最后一层开始从左至右按层遍历
所以面临以下问题:
1.如何根据广义表构造树(注意节点不是字母,而是数字(可能是多位数)!!!!!)
2.如何从最后一层依次向上面的层遍历
3.如何输出一层之后换行再输出另一行
针对问题2,只要在按层遍历的时候从右至左遍历,最后输出到数组里,再将数组逆序输出就好
针对问题3,就是设置一个东西来记录节点的层数。这里用到了二维数组,int a[][2],
    其中a[][0]用来记录节点的数字,a[][1]用来记录层数
针对问题1,解决办法是先用gets将广义表读入字符串s[],然后依次从开头往后读,
    发现‘0’~‘9’之间的字符,就放到字符数组num[]中,直到不再是‘0’~‘9’之间的字符,
    然后将num[]转换成数字(可以用sscanf)

#include<stdio.h>
#include<string.h>						// strlen(), memset()
#include<iostream> 
#define MAXSIZE 100
using namespace std;
typedef struct node
{
	int data;
	int depth;						//记录深度,同一深度输出到同一行
	struct node* lchild, * rchild;
}Node, * Tree;
int a[MAXSIZE][2];
Tree create()
{
Tree stack[MAXSIZE],p,T=NULL;
	char ch, s[MAXSIZE] = { 0 }, num[10] = { 0 };
	int flag, top = -1, len, i = 0, j = 0, sum = 0;
	fgets(s, MAXSIZE - 1, stdin);			//先读到数组中,这样做,数据就可以重复读了!功 能: 从流中读取一字符串
	len = strlen(s);
	while (1)
	{
		memset(num, '\0', 10);			//每次别忘了对num清零!
		ch = s[i];
		if (s[i] >= '0' && s[i] <= '9')	        //发现一个字符是'0'~'9',可能是多位数
		{
			j = 0;
			while (s[i] >= '0' && s[i] <= '9')
			{
				num[j++] = s[i++];
			}
			sscanf(num, "%d", &sum); //sscanf字符串转数字
		}
		else
			i++;					//如果不是数字,要i++,下次就可以读下一个字符

		if (ch == '\n' || i >= len)		        //发现'\n'或者是s[]到头了就返回
			return T;
		else
		{
			switch (ch)
			{
			case '(': stack[++top] = p;
				flag = 1;
				break;
			case ')': top--;
				break;
			case ',': flag = 2;
				break;
			default: p=new Node;
				p->data = sum;
				p->depth = 0xfffffff;//初始化
				p->lchild = NULL;
				p->rchild = NULL;
				if (T == NULL)
				{
					T = p;
					p->depth = 1;			//记录深度
				}
				else if (flag == 1)
				{
					stack[top]->lchild = p;
					p->depth = top + 2;	//记录深度
				}
				else
				{
					stack[top]->rchild = p;
					p->depth = top + 2;	//记录深度
				}
			}
		}
	}
}
int layer(Tree T)
{//用队列来实现层数遍历
	Tree queue[MAXSIZE], p;
	int front, rear, i = 0;
	if (T != NULL)
	{
		queue[0] = T;
		front = -1;
		rear = 0;
		while (front < rear)
		{
			p = queue[++front];
			a[i][0] = p->data;
			a[i++][1] = p->depth;				//将遍历的节点记录到a[][]
			if (p->rchild != NULL)				//先右,便是从右至左遍历
				queue[++rear] = p->rchild;
			if (p->lchild != NULL)
				queue[++rear] = p->lchild;
		}
	}
	return --i;							//返回节点个数!别忘了是--i
}
int main()
{
	Tree T;
	int i = 0, dep;
	T = create();					//根据广义表构建树
	i = layer(T);						//按层遍历,从右至左
	dep = a[i][1];
	for (; i >= 0; i--)					//遍历所有的节点以输出
	{
		if (dep != a[i][1])				//如果发现深度跟之前的不同,就换行
		{
			cout << endl;
			dep = a[i][1];
		}
		cout<<a[i][0]<<" ";
	}
	return 0;
}

求二叉树最大宽度的两种算法

【问题描述】求解二叉树的宽度,请用递归和非递归两种方法求解。
【输入形式】前序和中序序列建树
【输出形式】递归和非递归求解的宽度值
【样例输入】
abcdefg
cbdaegf
【样例输出】
3 3
【解析】本案例的树为:
       a             宽度为1
    b     e          宽度为2
  c   d  # f         宽度为3
 # # # #  # g        宽度为1
           # #

#include<iostream>
#include<vector>
#include<queue>
using namespace std;
typedef struct Node
{
    char date;
    struct Node* lchild, * rchild;
} Node, * Tree;

Tree build(int prestart, int instart, int inend, vector<char>preorder, vector<char>inorder)
{
    if (prestart >= preorder.size() || instart > inend)return NULL;
    Tree root = new Node;
    root->date = preorder[prestart];
    int inIndex = 0;
    for (int i = instart; i <= inend; i++)
    {//找到根节点
        if (inorder[i] == preorder[prestart])
        {
            inIndex = i; break;
        }
    }//根节点索引为inIndex
    //递归调用 创建这个根节点的左子树和柚子树
    root->lchild = build(prestart + 1, instart, inIndex - 1, preorder, inorder);
    root->rchild = build(prestart + 1 + inIndex - instart, inIndex + 1, inend, preorder, inorder);
    return root;
}
int Depth(Tree T)
{
    if (T == NULL)return 0;//退出条件
    int leftDepth = Depth(T->lchild);
    int rightDepth = Depth(T->rchild);
    return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
    //三目运算符 如果左深度大于右深度 左+1 否则 右+1
}
int Width(Tree T)
{//非递归算法求宽度
    queue<Tree>q;
    int count, max=0;//记录每一层宽度和最大宽度
    Tree temp;
    if (T == NULL)return max;
    q.push(T);//当前根入队
    while (!q.empty())//队列不为空
    {
        count = q.size();//当前层宽度等于队列长度
        if (count > max)max = count;//更新最大宽度
        for(int i=0;i<count;i++)
        {//每一层的结点入队
            temp = q.front(); q.pop();//获取队首,移除队首元素
            if (temp->lchild != NULL)//左子树非空 左子树入队
                q.push(temp->lchild);
            if (temp->rchild != NULL)
                q.push(temp->rchild);
        }
    }
    return max;
}
int Count[100];
int Max = -1;
void width(Tree T, int k)//k为当前结点所在层数
{//递归算法求最大宽度
    if (T == NULL)return;
    Count[k]++;//当前结点非空 记录当前层数的Count++
    if (Max < Count[k])Max = Count[k];//更新最大宽度
    width(T->lchild,k+1);
    width(T->rchild, k + 1);
}
int main()
{
    char a[100], b[100];
    cin >> a >> b;
    //    char p, q;
    //    cin >> p >> q;
    vector<char>inorder;
    vector<char>preorder;
    for (int i = 0; a[i]; i++)
    {
        preorder.push_back(a[i]);
        inorder.push_back(b[i]);
    }
    //cout << inorder.size() << preorder.size();
    Tree T;
    T = build(0, 0, inorder.size() - 1, preorder, inorder);
    width(T,0);//递归求最大宽度;
    cout << Width(T)<<" "<<Max;
    return 0;
}

最优二叉树(哈夫曼树) 哈夫曼编码

【问题描述】
读入n个字符所对应的权值,构造一棵哈夫曼树,自底向上生成每一个字符对应的哈夫曼编码,并依次输出。
【输入形式】
输入的第一行包含一个正整数n,表示共有n个字符需要编码。其中n不超过100。
第二行中有n个用空格隔开的正整数,分别表示n个字符的权值。
【输出形式】
共n行,每行一个字符串,表示对应字符的哈夫曼编码。
【注意】保证每次左子树比右子树的权值小;如出现相同权值的,则先出现的在左子树。
【样例输入】
8
5 29 7 8 14 23 3 11
【样例输出】
0001
10
1110
1111
110
01
0000
001

[解析]
输入
30 5 10 20 
哈夫曼编码表:前四个是叶子 后面2*n-1到n+1编号为分支节点 
HT        1    2    3     4    5       6     7
weight    30    5    10   20      15   35    65
parent  7   5   5    6    6    7     0
lchild  0   0   0    0    2    5     1
rchild  0   0   0    0    3    4     6
哈夫曼树:
       65
 (1)30     (0)35
         (1)15  (0)20
       (1)5  (0)10
哈夫曼编码:
30 :'0'
5  :'100'
10 :'101'
20 :'11'

#include<iostream>
#include<string.h>
#include<stdio.h>
#define MAX 20000
using namespace std;
int n;//叶子结点个数
int m;//总结点个数
typedef struct
{
	int weight;//权重
	int parent;//父节点
	int lchild;//左孩子
	int rchild;//右孩子
}HTNode;
typedef HTNode HuffmanTree[MAX];//哈夫曼树
typedef char* HuffmanCode[MAX];//哈夫曼编码

void select(HuffmanTree HT, int k, int& s1, int& s2)
{//寻找叶子结点1和k之前权最小的两个点 记录编号s1s2并且返回
	int tmp = MAX, tmpi = 0;
	for (int i = 1; i <= k; i++)
	{//第一次查找叶子1和k之间最小的点
		if (!HT[i].parent)
		{//无parent

			if (tmp > HT[i].weight)
			{
				tmp = HT[i].weight;
				tmpi = i;
			}
		}
	}
	s1 = tmpi;//找到了第一个最小的点
	tmp = MAX; tmpi = 0;
	for (int i = 1; i <= k; i++)
	{
		if ((!HT[i].parent) && i != s1)
		{//无parent且跟s1相异
			if (tmp > HT[i].weight)
			{
				tmp = HT[i].weight;
				tmpi = i;
			}
		}
	}
	s2 = tmpi;
}
void creatrHuffmanTree(HuffmanTree& HT, int* w)
{
	for (int i = 1; i <= n; i++)
	{//叶子初始化
		HT[i].weight = w[i];
		HT[i].lchild = 0;
		HT[i].rchild = 0;
		HT[i].parent = 0;
	}
	for (int i = n + 1; i <= m; i++)
	{//初始化分支节点 从n+1到2*n-1=m
		HT[i].weight = 0;
		HT[i].lchild = 0;
		HT[i].parent = 0;
		HT[i].rchild = 0;
	}
	for (int i = n + 1; i <= m; i++)
	{
		int s1, s2;
		select(HT, i - 1, s1, s2);//查找目前未连接的结点权最小的两个结点的编号
		HT[s1].parent = i;//构建parent结点
		HT[s2].parent = i;
		if (HT[s1].weight > HT[s2].weight)
		{
			HT[i].lchild = s1;
			HT[i].rchild = s2;
		}
		else
		{
			HT[i].lchild = s2;
			HT[i].rchild = s1;
		}
		HT[i].weight = HT[s1].weight + HT[s2].weight;
	}
}
void encodingHuffmanCode(HuffmanTree HT, HuffmanCode& HC)
{//根据哈夫曼树生成哈夫曼编码HC
	char tmp[MAX];
	tmp[n - 1] = '\0';
	int start, child, father;
	for (int i = 1; i <= n; i++)
	{//根据叶子结点 向上追溯 到根节点退出
		start = n - 1;
		child = i;
		father = HT[i].parent;
		while (father)
		{//追溯到根结点就退出
			if (HT[father].lchild == child)tmp[--start] = '1';//如果左孩子存在编码1
			else tmp[--start] = '0';//右孩子存在 编码为0
			child = father;//向上遍历
			father = HT[father].parent;
		}
		HC[i] = new char[n - start];
		strcpy(HC[i], &tmp[start]);
	}
}
void displayHuffmanCoding(HuffmanCode HC) {
	for (int i = 1; i <= n; i++) {
		printf("%s\n", HC[i]);
	}
	cout<<endl;
}
int main()
{
	cin >> n;
	m=2*n-1;
	int w[MAX];
	for (int i = 1; i <= n; i++)
	{
		cin >> w[i];
	}
	HuffmanTree HT;
	HuffmanCode HC;
	creatrHuffmanTree(HT, w);
	encodingHuffmanCode(HT, HC);
	displayHuffmanCoding(HC);
	return 0;
}

哈夫曼树最短带权路径

【问题描述】
 已知输入一串正整数,正整数之间用空格键分开,请建立一个哈夫曼树,以输入的数字为叶节点,求这棵哈夫曼树的带权路径长度。
【输入形式】
 首先输入正整数的个数,然后接下来为接下来的正整数,正整数个数不超过10个
【输出形式】
 输出相应的权值
【样例输入】
 5 
 4 5 6 7 8
【样例输出】
 69
【解析】
输入
4
30 5 10 20
哈夫曼编码表:前四个是叶子 后面2*n-1到n+1编号为分支节点
HT        1    2    3     4    5       6     7
weight    30    5    10   20      15   35    65
parent  7   5   5    6    6    7     0
lchild  0   0   0    0    2    5     1
rchild  0   0   0    0    3    4     6
depth   1   3   3    2    0    0     0
哈夫曼树:
       65
    30     35
         15  20
       5  10
带权路径长度=叶子权*叶子深度 求和
W(T)=30*1+5*3+10*3+20*2=115

#include<iostream>
#define MAX 20000
using namespace std;
int N;//叶子结点个数
int M;//总结点个数
typedef struct
{
	int weight;//权重
	int parent;//父节点
	int lchild;//左孩子
	int rchild;//右孩子
}HTNode;
typedef HTNode HuffmanTree[MAX];//哈夫曼树
void select(HuffmanTree HT, int k, int& s1, int& s2)
{//寻找结点1到结点k之间最小权的两个结点 编号记为s1 s2
	int tmp = MAX, tmpi = 0;
	for (int i = 1; i <= k; i++)
	{//第一次查找叶子1和k之间最小权的结点
		if (!HT[i].parent)
		{//无parent 可以构建
			if (tmp > HT[i].weight)
			{
				tmp = HT[i].weight;
				tmpi = i;
			}
		}
	}
	s1 = tmpi;//找到了第一个权最小的结点编号为s1
	tmp = MAX; tmpi = 0;
	for (int i = 1; i <= k; i++)
	{
		if ((!HT[i].parent) && i != s1)
		{//无parent且与s1相异的点
			if (tmp > HT[i].weight)
			{
				tmp = HT[i].weight;
				tmpi = i;
			}
		}
	}
	s2 = tmpi;
}
void createHuffmanTree(HuffmanTree& HT, int* w)
{//w为权数组
	for (int i = 1; i <= N; i++)
	{//叶子哈夫曼表初始化
		HT[i].weight = w[i];
		HT[i].lchild = 0;
		HT[i].rchild = 0;
		HT[i].parent = 0;
		
	}
	for (int i = N + 1; i <= M; i++)
	{//初始化分支节点 从n+
		HT[i].weight = 0;
		HT[i].lchild = 0;
		HT[i].parent = 0;
		HT[i].rchild = 0;
	
	}
	for (int i = N + 1; i <= M; i++)
	{
		int s1, s2;
		select(HT, i - 1, s1, s2);
		HT[s1].parent = i;
		HT[s2].parent = i;
		HT[i].lchild = s1;
		HT[i].rchild = s2;
		HT[i].weight = HT[s1].weight + HT[s2].weight;
	}
}
/*计算机步骤:
1.第一个for和第二个for循环后得到
HT		1	2	3	 4    5	   6	 7
weight	30	5	10   20	  0    0     0
parent  0   0   0    0    0    0     0
lchild  0   0   0    0    0    0     0
rchild  0   0   0    0    0    0     0
第三个for循环详细步骤:
第一次: i=5 s1=2 s2=3 
更新哈夫曼表:
HT		1	2	3	 4    5	   6	 7
weight	30	5	10   20	  15   0     0
parent  0   5   5    0    0    0     0
lchild  0   0   0    0    2    0     0
rchild  0   0   0    0    3    0     0
第二次:i=6 s1=4 s2=5
更新哈夫曼表:
HT		1	2	3	 4    5	   6	 7
weight	30	5	10   20	  15   35    0
parent  0   5   5    6    6    0     0
lchild  0   0   0    0    2    4     0
rchild  0   0   0    0    3    5     0
第三次:i=7 s1=1 s2=6
更新哈夫曼表:
HT		1	2	3	 4    5	   6	 7
weight	30	5	10   20	  15   35    65
parent  7   5   5    6    6    7     0
lchild  0   0   0    0    2    4     1
rchild  0   0   0    0    3    5     6
*/
int WPL(HuffmanTree HT, int i, int depth)
{//递归计算最短带权路径长度
	if (HT[i].lchild == 0 && HT[i].rchild == 0)
	{//i为叶子结点
		return HT[i].weight * depth;
	}
	else
	{
		return WPL(HT, HT[i].lchild, depth + 1) + WPL(HT, HT[i].rchild, depth + 1);
	}
}
/*
* 【样例输入】
4
30 5 10 20
 哈夫曼树:
	   65
	30     35
		 15  20
	   5  10
HT		1	2	3	 4    5	   6	 7
weight	30	5	10   20	  15   35    65
parent  7   5   5    6    6    7     0
lchild  0   0   0    0    2    5     1
rchild  0   0   0    0    3    4     6
depth   1   3   3    2    0    0     0
计算机步骤:
step1:i=2*N-1=M=7 WPL(HT,7的左子树编号:1,深度:1)+WPL(HT,7的右子树编号:6,深度:1)
step2:i=1	编号为1的结点的左右为空 是叶子结点 返回权*深度:30*1 返回到step1的左WPL
step3:i=6   WPL(HT,6的左子树编号:5 深度:2)+WPL(HT,6的右子树编号:4 深度:2)
step4:i=5   WPL(HT,5的左子树编号:2,深度:3)+WPL(HT,5的右子树编号:3 深度:3)
step5:i=2  编号为2的结点的左右为空 是叶子结点 返回权*深度:5*3 返回到setp4的左WPL
step6:i=3  编号为3的结点是叶子结点 返回权*深度:10*3 返回到setp4的右WPL 回到setp4:5*3+10*3整体作为一个结果返回到step3的左WPL
step7:i=4  编号为4的结点是叶子结点 返回权*深度:20*2 返回到stpe3的右WPL 回到step3:5*3+10*3+20*2整体作为一个结果返回到step1的右WPL
step8: 返回step1的左右WPL到主函数:30*1+5*3+10*3+20*2=115
*/
int main()
{
	HuffmanTree T;
	cin >> N;//叶子结点个数
	M = 2 * N - 1;//总结点个数
	int w[MAX];
	for (int i = 1; i <= N; i++)
	{
		cin >> w[i];
	}
	createHuffmanTree(T, w);
	cout << WPL(T, M, 0);
	return 0;

}

中序线索二叉树

【问题描述】创建一棵二叉树,接着中序线索化该二叉树,然后编写相应函数遍历该中序线索二叉树
【编码要求】线索二叉树遍历过程中不能使用递归、不能使用栈。
【输入形式】二叉树拓展的前序遍历序列
【输出形式】中序遍历序列
【样例输入】AB#D##CE###
【样例输出】BDAEC

【解析】
【样例输入2】
ABC##DE#G##F###
【输出样例2】
CBEGDFA
中序二叉树:
          A
    B           #
 C     D
# #  E    F
    # G  # #
     # #
中序遍历:CBEGDFA
标记前驱后继结点:
如果有左孩子Lchild就指向左孩子,Ltag=0
没有的话Lchild指向遍历前驱,Ltag=1
如果有右孩子Rchild就指向右孩子,Rtag=0
没有的话Rchild指向遍历后继,Rtag=1

#include<iostream>
using namespace std;
typedef struct Node
{
    char date;
    struct Node* lchild, * rchild;
    int Ltag, Rtag;//标记前驱后继结点  如果有左孩子Lchild就指向左孩子,Ltag=0||没有的话Lchild指向遍历前驱,Ltag=1
    //如果有右孩子Rchild就指向右孩子,Rtag=0 ||没有的话Rchild指向遍历后继,Rtag=1
} Node, * Tree;
void build(Tree& T)
{//根据先序扩展构建二叉树
    char ch;
    cin >> ch;
    if (ch == '#')T = NULL;
    else if (ch == '\n')return;
    else
    {
        T = new Node;
        T->date = ch;
        T->Ltag = 0;
        T->Rtag = 0;
        build(T->lchild);
        build(T->rchild);
    }
}
Node* pre;//前驱结点
void Inthread(Tree root)
{//二叉树中序线索化 更新结点的Ltag和Rtag
    if (root!=NULL)
    {
        Inthread(root->lchild);//线索化左子树
        if (root->lchild == NULL)
        {//没有左孩子,Ltag=1 左孩子指向前驱
            root->lchild = pre;
            root->Ltag = 1;
        }
        if (pre!=NULL && pre->rchild==NULL)
        {//前驱非空 且 前驱没有右孩子 前驱Rtag=1 前驱右孩子指向当前的结点
            pre->rchild = root;
            pre->Rtag = 1;
        }
        pre = root;
        Inthread(root->rchild);//线索化右子树
    }
}
void TInOrder(Tree BT)
{//根据中序线索化二叉树中序遍历输出
    Tree p = BT;
    while (p != NULL)
    {//当前结点非空
        while (p->Ltag == 0)
            //在线索树中找到中序遍历第一个结点
            p = p->lchild;
        cout << p->date;//输出中序遍历第一个结点
        while (p->Rtag == 1 && p->rchild != NULL)
        {//如果有后继结点且右子树非空
            p = p->rchild;//移步到右子树
            cout << p->date;
        }
        p = p->rchild;//移步到右子树 中序遍历:左中右
    }
}
int main()
{
    Tree T;
    build(T);//先序扩展建立二叉树
    Inthread(T);//中序线索化
    pre->Rtag = 1;//前驱结点有后继
    TInOrder(T);//根据中序线索化二叉树中序输出
    return 0;
}

五、图

图的邻接表创建

数据结构图的创建
图的分类:
无向图 有向图
存储结构:
邻接矩阵 邻接表
输入解析
顶点数 边数
顶点信息
边的信息(起点 终点)
输入样例1:
4 5
a b c d
a b
b c
c d
d a
b a
5 6
a b c d e 
a b
b c
c d
d e
e a
a c
f a
输入样例2:
5 6
1 2 3 4 5
1 2
1 4
2 3
2 5
3 4
3 5
5 6
1 2 3 4 5
1 2
1 4
2 3
2 5
3 4
3 5

#include<iostream>
using namespace std;
#define ElemType char//顶点的数据类型
#define MAX 20 //最大顶点个数
typedef struct ArcNode
{
	int addressvex;//存储的是该顶点数组中的位置
	struct ArcNode* next;
}ArcNode;//边表

typedef struct VNode
{
	ElemType vertex;//是哪一个顶点
	struct ArcNode* firstarc;
}VNode;//单个顶点

typedef struct
{
	VNode AdjList[MAX];//顶点构成的结构体数组
	int vexnum, arcnum;//顶点数和边数
	int kind;//图的类型: 无向图 有向图
}ALGraph;//图的结构定义

int LocateVex(ALGraph* G, ElemType c)
{//在顶点表中查找顶点的位置
	
	for (int i = 1; i <= G->vexnum; i++)
	{
		if (c == G->AdjList[i].vertex)return i;
	}
	cout << "没有找到点" << c << endl; return -1;
}

void CreateDG(ALGraph* G)
{//创建有向图
	//1.输入顶点数和边数
	cin >> G->vexnum >> G->arcnum;
	//2.顶点表数据填值,初始化顶点表指针域
	for (int i = 1; i <= G->vexnum; i++)
	{
		cin >> G->AdjList[i].vertex;
		G->AdjList[i].firstarc = NULL;
	}
	//3.输入边的信息构造邻接表 
	ElemType v1, v2;
	int n, m;//记住点在顶点表中的位置
	ArcNode* p;
	for (int i = 1; i <= G->arcnum; i++)
	{
		cin >> v1 >> v2;
		n = LocateVex(G, v1);
		m = LocateVex(G, v2);
		if (n == -1 || m == -1)
		{
			cout << "没有这个顶点!" << endl; return;
		}
		p = new ArcNode;
		p->addressvex = m;
		p->next = G->AdjList[n].firstarc;//头插法
		G->AdjList[n].firstarc = p;
	}
	G->kind = 1;
}
void CreateUDG(ALGraph* G)
{//创建无向图
	//#1.输入顶点数和边数
	cin >> G->vexnum >> G->arcnum;
	//2.输入顶点元素
	for (int i = 1; i <= G->vexnum; i++)
	{
		cin >> G->AdjList[i].vertex;
		G->AdjList[i].firstarc = NULL;
	}
	//3.输入边信息构造邻接表

	ElemType v1, v2;
	int n, m;//记住点在顶点表中的位置
	ArcNode* p1,  *p2;
	for (int i = 1; i <= G->arcnum; i++)
	{
		cin >> v1 >> v2;
		n = LocateVex(G, v1);
		m = LocateVex(G, v2);
		if (n == -1 || m == -1)
		{
			cout << "没有找到该点!" << endl;
			return;
		}
		p1 = new ArcNode;
		p1->addressvex = m;//第二个点在第一个点的数组中的位置
		p1->next = G->AdjList[n].firstarc;//头插法
		G->AdjList[n].firstarc = p1;

		p2 = new ArcNode;
		p2->addressvex = n;
		p2->next = G->AdjList[m].firstarc;
		G->AdjList[m].firstarc = p2;
	}
	G->kind = 2;
}
void display(ALGraph G)
{//打印邻接表
	ArcNode* p;
	for (int i = 1; i <= G.vexnum; i++)
	{
		cout << endl << G.AdjList[i].vertex;
		p = G.AdjList[i].firstarc;
		while (p != NULL)
		{//遍历i为起点的其他边
			cout << "-->" << p->addressvex;
			p = p->next;
		}
	}
	cout << endl;
}
int main()
{
	ALGraph G,D;
	cout << "创建有向图:" << endl;
	CreateDG(&G);
	display(G);
	cout << "创建无向图:" << endl;
	CreateUDG(&D);
	display(D);
}

图的邻接矩阵

数据结构图的创建
图的分类:
无向图 有向图
存储结构:
邻接矩阵 邻接表
输入解析
顶点数 边数
顶点信息
边的信息(起点 终点)
输入样例1(带权有向图+不带权的无向图):
5 8
1 2 3 4 5
1 2 4
2 1 5
1 3 6
3 1 7
3 4 8
3 5 6
5 3 9
4 3 10
4 5
a b c d
1 2 1 
2 3 1
3 4 1
4 1 1
2 1 1
输入样例2(带权的有向图+带权的无向图)
5 8
1 2 3 4 5
1 2 4
2 1 5
1 3 6
3 1 7
3 4 8
3 5 6
5 3 9
4 3 10
5 6
a b c d e
1 2 10
2 3 11 
3 4 5
4 5 4
5 1 6
1 3 8 
1 4 520
 

#include<iostream>
using namespace std;
#define MAX 20

typedef int EdgeType;//边类型
typedef char VertexType;//顶点类型
typedef struct
{
	VertexType vex[MAX];//顶点数组
	EdgeType Edge[MAX][MAX];//领结数组
	int vexnum, edgenum;//顶点数和边数
	int kind;//类型 1是有向图 2是无向图
}AdjMatrix;
void CreateGraph(AdjMatrix* G,int kind)
{
	G->kind = 1;
	//#1.输入顶点数边数  初始化领接矩阵
	cin >> G->vexnum >> G->edgenum;
	for (int i = 1; i <= G->vexnum; i++)
	{
		for (int j = 1; j <= G->vexnum; j++)
		{
			if (i == j) G->Edge[i][j] = 0;
			else		G->Edge[i][j] = 32767;
		}
	}
	//#2.顶点信息 边信息

	for (int i = 1; i <= G->vexnum;i++)
	{
		cin >> G->vex[i];
	}
	int v1, v2;
	int w;
	for (int i = 1; i <= G->edgenum; i++)
	{
		cin >> v1 >> v2 >>w ;
		//若为不带权值的图 则w输入1
		//若为带权值的图 则w输入对应权值
		if (G->kind == 1) { G->Edge[v1][v2] = w; continue; }//①
		else
		{//无向图
			G->Edge[v1][v2] = w;//①
			G->Edge[v2][v1] = w;//②
		}
		//无向图具有对称性 通过①②实现
		//有向图没有对称性 只需要①
	}
	
}
void Display(AdjMatrix G)
{//输出领借矩阵
	
	for (int i = 1; i <= G.vexnum; i++)
	{
		cout<<"    "<< G.vex[i] << " ";
	}

	for (int i = 1; i <= G.vexnum; i++)
	{
		cout << endl<<G.vex[i]<<"    ";
		for (int j = 1; j <= G.vexnum; j++)
		{
			if (G.Edge[i][j] == 32767)
				cout << "∞    ";
			else
				cout<<G.Edge[i][j]<<"    ";
		}
		cout << endl;
	}
}
int main()
{
	AdjMatrix G;
	cout << "构建有向图:" << endl;
	CreateGraph(&G,1); Display(G);
	cout << "构建无向图:" << endl;
	CreateGraph(&G, 2); Display(G);
}

尾插法建图 BFS广度优先搜索

输入样例1(带权有向图+不带权的无向图):
5 8
1 2 3 4 5
1 2
2 1
1 3
3 1
3 4
3 5
5 3
4 3
输入2:
4 5
a b c d
1 2
2 3
3 4
4 1
2 1

概念
广度优先搜索遍历类似于树的按层次遍历的过程。
广度优先搜索算法(又称宽度优先搜索)是最简便的图的搜索算法之一,
这一算法也是很多重要的图的算法的原型。
其别名又叫BFS,属于一种盲目搜寻法,
目的是系统地展开并检查图中的所有节点,以找寻结果。
换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。
对于连通图(图如上深度搜索的连通图)
1.从图中某个顶点出发,访问v;
2.依次访问v的各个未曾访问过的邻接点;
3.分别从这些邻接点出发依次访问它们的邻接点,
并使“先被访问的顶点的邻接点”先于“后被访问的邻接点”被访问。
重复步骤3,直至图中所有已被访问的顶点的邻接点都被访问到。

广度优先搜索的特点是:
尽可能先对横向进行搜索。
设x和y是两个相继被访问过的顶点,若当前以x为出发点进行搜索,
则在访问x的所有未被访问过的邻接点之后,紧接着是以y为出发点进行横向搜索,
y的邻接点中尚未被访问的顶点进行访问。
也就是说,先访问的顶点其邻接点亦先被访问。
为此,算法实现时需要引进队列保存已被访问过的顶点。

#include<iostream>
#include<queue>
using namespace std;
#define MAX 20
typedef int EdgeType;//边类型
typedef char VertexType;//顶点类型
typedef struct
{
	VertexType vex[MAX];//顶点数组
	EdgeType Edge[MAX][MAX];//领结数组
	int vexnum, edgenum;//顶点数和边数
	int kind;//类型 1是有向图 2是无向图
}AdjMatrix;
void CreateGraph(AdjMatrix* G, int kind)
{
	G->kind = 1;
	//#1.输入顶点数边数  初始化领接矩阵
	cin >> G->vexnum >> G->edgenum;
	for (int i = 1; i <= G->vexnum; i++)
	{
		for (int j = 1; j <= G->vexnum; j++)
		{
			G->Edge[i][j] = 0;
		}
	}
	//#2.顶点信息 边信息

	for (int i = 1; i <= G->vexnum; i++)
	{
		cin >> G->vex[i];
	}
	int v1, v2;
	for (int i = 1; i <= G->edgenum; i++)
	{
		cin >> v1 >> v2;
			G->Edge[v1][v2] = 1;
			G->Edge[v2][v1] = 1;
	}

}
int visited[MAX];//如果被访问 ==1
void BFS(AdjMatrix* G, int i)
{//广度优先搜索
	queue<int> q;
	cout << G->vex[i]<< " ";//输出起点
	visited[i] = 1;//设置为已经访问
	q.push(i);//将第一个结点入队
	while (!q.empty())//队列非空
	{
		i = q.front(); q.pop();
		for (int j = 1; j <= G->vexnum; j++)
		{
			if (G->Edge[i][j] == 1 && visited[j] != 1)
			{
				cout << G->vex[j] << " ";
				visited[j] = 1;//已经访问
				q.push(j);
			}
		}
	}
}
int main()
{
	AdjMatrix G, D;
	//cout << "创建有向图:" << endl;
	//CreateGraph(&G,1);
	//cout << "BFS:" << endl;
	//BFS(&G,1);
	cout <<endl<< "创建无向图:" << endl;
	CreateGraph(&D,1);
	cout << "BFS:" << endl;
	BFS(&D,1);
}

尾插法建图DFS深度优先搜索

输入样例1:
4 5
a b c d
a b
b c
c d
d a
b a
5 6
a b c d e
a b
b c
c d
d e
e a
a c
f a
输入样例2:
5 6
1 2 3 4 5
1 2
1 4
2 3
2 5
3 4
3 5
5 6
1 2 3 4 5
1 2
1 4
2 3
2 5
3 4
3 5
图的结构:多对多的关系
图中有点和边,点:数据,边:描述点之间的关系
主要描述方式:邻接矩阵,邻接表
邻接矩阵:基于数组->用一个一维数组存储顶点,用一个二位数组存储边
邻接表:基于链表->用一个一维数组或者一个链表来存储所有顶点
其中每个顶点都是一个链表的头结点,后面跟着所有能通过边到达的顶点
 

#include<iostream>
using namespace std;
#define ElemType char//顶点的数据类型
#define MAX 20 //最大顶点个数
typedef struct ArcNode
{
	int addressvex;//存储的是该顶点数组中的位置
	struct ArcNode* next;
}ArcNode;//边表

typedef struct VNode
{
	ElemType vertex;//是哪一个顶点
	struct ArcNode* firstarc;
}VNode;//单个顶点

typedef struct
{
	VNode AdjList[MAX];//顶点构成的结构体数组
	int vexnum, arcnum;//顶点数和边数
	int kind;//图的类型: 无向图 有向图
}ALGraph;//图的结构定义

int LocateVex(ALGraph* G, ElemType c)
{//在顶点表中查找顶点的位置

	for (int i = 1; i <= G->vexnum; i++)
	{
		if (c == G->AdjList[i].vertex)return i;
	}
	cout << "没有找到点" << c << endl; return -1;
}
void CreateDG(ALGraph* G)
{//创建有向图
	//1.输入顶点数和边数
	cin >> G->vexnum >> G->arcnum;
	//2.顶点表数据填值,初始化顶点表指针域
	for (int i = 1; i <= G->vexnum; i++)
	{
		cin >> G->AdjList[i].vertex;
		G->AdjList[i].firstarc = NULL;
	}
	//3.输入边的信息构造邻接表 
	ElemType v1, v2;
	int n, m;//记住点在顶点表中的位置
	ArcNode* p;
	for (int i = 1; i <= G->arcnum; i++)
	{
		cin >> v1 >> v2;
		n = LocateVex(G, v1);
		m = LocateVex(G, v2);
		if (n == -1 || m == -1)
		{
			cout << "没有这个顶点!" << endl; return;
		}
		p = new ArcNode;
		p->addressvex = m;
		p->next = G->AdjList[n].firstarc;//头插法
		G->AdjList[n].firstarc = p;
	}
	G->kind = 1;
}
void CreateUDG(ALGraph* G)
{//创建无向图
	//#1.输入顶点数和边数
	cin >> G->vexnum >> G->arcnum;
	//2.输入顶点元素
	for (int i = 1; i <= G->vexnum; i++)
	{
		cin >> G->AdjList[i].vertex;
		G->AdjList[i].firstarc = NULL;
	}
	//3.输入边信息构造邻接表

	ElemType v1, v2;
	int n, m;//记住点在顶点表中的位置
	ArcNode* p1, * p2,*head,*head2;
	for (int i = 1; i <= G->arcnum; i++)
	{
		cin >> v1 >> v2;
		n = LocateVex(G, v1);
		m = LocateVex(G, v2);
		if (n == -1 || m == -1)
		{
			cout << "没有找到该点!" << endl;
			return;
		}
		p1 = new ArcNode;
		p1->addressvex = m;//第二个点在第一个点的数组中的位置
		p1->next = NULL;//尾插法
		head = G->AdjList[n].firstarc;
		if (head == NULL)
		{
			G->AdjList[n].firstarc = p1;
		}
		else
		{
			while (head->next != NULL)
			{
				head = head->next;
			}
			head->next = p1;
		}

		p2 = new ArcNode;
		p2->addressvex = n;
		p2->next = NULL;//尾插法
		head2 = G->AdjList[m].firstarc;
		if (head2 == NULL)
		{
			G->AdjList[m].firstarc = p2;
		}
		else
		{
			while (head2->next != NULL)
			{
				head2 = head2->next;
			}
			head2->next = p2;
		}
	}
	G->kind = 2;
}

int visited[MAX];//如果被访问 ==1
/*对于一个连通图,深度优先搜索遍历过程:
1.从图中某个顶点v出发,访问v;
2.找出刚刚访问过的顶点的第一个未被访问的邻接点,访问该顶点。以该顶点为新顶点,重复该步骤,
直至刚访问过的顶点没有未被访问的邻接点为止;(递归思想)
3.返回前一个访问过的且仍有未被访问的邻接点的顶点,找出该顶点的下一个未被访问的邻接点,访问该顶点;
4.重复第2、3步骤,直至图中所有顶点都被访问过,搜索结束。
*/
void DFS(ALGraph* G, int i)
{//深度优先搜索
	ArcNode* p;
	cout << G->AdjList[i].vertex<<"  ";
	visited[i] = 1;//当前结点被访问
	p = G->AdjList[i].firstarc;
	while (p)
	{
		if (!visited[p->addressvex])//如果该点没有被访问就深度搜索该点
			DFS(G, p->addressvex);
		p = p->next;
	}
}
void DFSTraverse(ALGraph* G)
{
	for (int i = 1; i <= G->vexnum; i++)
	{//初始化
		visited[i] = 0;
	}
	for (int i = 1; i <= G->vexnum; i++)
	{
		if (!visited[i])DFS(G, i);//深度优先搜索
		cout << endl;
	}
}

int main()
{
	ALGraph G, D;
	cout << "创建有向图:" << endl;
	CreateDG(&G);
	cout << "DFS:" << endl;
	DFSTraverse(&G);
	cout << "创建无向图:" << endl;
	CreateUDG(&D);
	cout << "DFS:" << endl;
	DFSTraverse(&D);
}

最小生成树kruskal算法

如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz。
输入格式:
第一行包含两个整数 N,M,表示该图共有 N个结点和 M 条无向边。
接下来 M 行每行包含三个整数 Xi,Yi,Zi? 表示有一条长度为 Zi ?的无向边连接结点 Xi,Yi ?
输出格式:
如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 orz。
输入输出样例
输入 #1复制
4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
输出
7
处理过程:
根据权的大小给边数组排序:
1 2 2
1 3 2
1 4 3
3 4 3
2 3 4
再通过并查集判断某条边的两个端点是不是同一个组
如果不是那就连起来  所连边数+1 距离更新一下
如果是就跳到下一组边的两个端点判断
step1:遍历边1 检查端点1和端点2分别属于组1和组2 不同组 所以连起来  更新端点1属于组2 
        当前所连条数:1 长度:2
step2:遍历边2检查端点1和端点3分别属于组1和组3不同组 所以连起来 point[端点1的组:2]点2的组=组3
        当前所连条数:2 长度:2+2
step3:遍历边3检查端点1和端点4分别属于组1和组4不同组 所以连起来 point[端点1的组:2]点2的组=组4
        当前所连条数:3 长度:2+2+3
四个点构成树最多三条边条件满足可以退出
点 1 2 3 4
组 2 4 3 4
step4:遍历边4检查端点2和端点3 都属于组1 不更新
step5:遍历边5检查端点3和端点4都属于组1 不更新
并查集:https://blog.csdn.net/bjweimengshu/article/details/108332389

#include<iostream>
#include<algorithm> //用到排序函数sort
using namespace std;
struct Line
{
	int a, b, lenth;//端点a  b 长度(权)lenth
};
bool cmp(Line l1, Line l2)
{//比较边l1和l2的长度(权)如果小于就返回1
	return l1.lenth < l2.lenth;
}
Line lines[1024];//存储边的数组
int point[1024];//点的数组 用法:标记每个点都是哪个组的
int getFather(int x)
{//并查集:
	return point[x] == x ? x : point[x] = getFather(point[x]);
	//如果相等 返回x
}
int main()
{
	int N, M;
	cin >> N >> M;
	for (int i = 1; i <= M; i++)
	{//输入M条边的端点和权
		cin >> lines[i].a >> lines[i].b >> lines[i].lenth;
	}
	sort(lines + 1, lines + 1 + M, cmp);//从lines[1]到lines[M] 按照cmp函数规则排序
	for (int i = 1; i <= N; i++)point[i] = i;//并查集初始化  默认各个点分组为其本身:点1 就是1组 点5就是5组
	int total = 0, distance = 0;//表示现在最小树所连接的边数 距离(权)
	for (int i = 1; total < N - 1; i++)
	{//N个点最多也就N-1个边遍历到连上N-1个边为止
		int groupnumber_a = getFather(lines[i].a);//利用并查集,找到两个端点所在组的组号
		int groupnumber_b = getFather(lines[i].b);
		if (groupnumber_a != groupnumber_b)
		{
			point[groupnumber_a] = point[groupnumber_b];
			total++;//当前所连的边数+1
			distance += lines[i].lenth;
		}
	}
	
	cout << distance;
}

最小生成树--prime算法

如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz。
输入格式:
第一行包含两个整数 N,M,表示该图共有 N个结点和 M 条无向边。
接下来 M 行每行包含三个整数 Xi,Yi,Zi 表示有一条长度为 Zi 的无向边连接结点 Xi,Yi 
输出格式:
如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 orz。
输入输出样例
输入 
4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
输出
7
处理过程:
last=1
首先从点1开始 探索到1-2 1-3 1-4 三条边 选择最小权的边进行连接:连接1-2 生成树条数:1 长度:2 last=2
探点2 探索到 1-3 1-4 2-3 三条边 选择权最小且未连接的边进行连接:连接1-3 生成树条数:2 长度2+2 last=3
探索点 3 探索到1-4 2-3 3-4 三条边 选择权最小的边进行连接:连接1-4 生成树条数:3 长度2+2+3
4个点已经连接了3条边退出 输出长度为7

以点1为起点的边 way[1]中:  (同时更新以点i为起点以点1为终点的边way[i])
输入:1 2 2 1 3 2 1 4 3
way[1][0].to:2  权:2     (way[2][0].to:1 权2)
way[1][1].to:3  权:2     (way[3][0].to:1 权2)
way[1][2].to:4  权:3     (way[4][0].to:1 权3)
输入:2 3 4 3 4 3
way[2][1].to:3  权:4     (way[3][1].to:2 权4)
way[3][1].to:4  权:3     (way[4][1].to:3 权3)
然后设置点i处到其他点最短距离:
distence[i] = 0x3f3f3f3f   i:1-n 设置为无穷
然后初始化g[](g[1]=1表示点1已连接 g[2]=0表示点2未连接)全部设置为0 未连接 再设置g[1]=1 已连接
然后设置last表示当前需要判断连接哪条边  的起点 初始化时候last=1 表示从点1开始判断要连哪条边
开始大循环:循环退出条件(已经连好n-1条边 cnt<n 每循环一次cnt++)
小循环1:遍历点last的way数组 设置终点的distance
(第一次大循环)循环后得到的结果:
distance[2]=way[1][0]=2
distance[3]=2
distance[4]=3
小循环2:遍历点1到点n
(第一次大循环)循环后得到的结果:
min_dis等于distance[1]到distance[4]中最小的一个值:
min_dis=distance[2]=2
min_to=i=2
相当于找到了点1为起点 所有可能终点中 权最短的边1-2 将其连接
设置last=min_to=2 更新last起点
cnt=1 更新总连接的边数
g[last]=1 更新该次循环中起点last状态设置为已连接 : 点1已连接
last=2进入第二次大循环:
(第二次大循环)小循环1后得到的结果:
distance[1]=2 因为点1已经连接过这时这个数据已经没有用了
distance[3]=2     (2<4所以取小的)
distance[4]仍然是3没有更新
(第二次大循环)小循环2后得到的结果:
min_dis=distance数组中最小且没有连接过的也就是diatance[3]=2
min_to=3

这个算法的动态演示:
https://www.bilibili.com/video/BV1gM4y1L7aN
这个算法的代码演示:
https://www.bilibili.com/video/BV1W44y1177D
这个算法用到了容器vector CSDN用法补充:
https://blog.csdn.net/msdnwolaile/article/details/52708144

#include<iostream>
#include<vector>
using namespace std;
struct Line {
	int to, len;//to表示这条边指向哪 len表示权(长度)
};
int distence[5005];//从没连接的组到已连接的组中每个点的最短距离
int g[5005];//组号存储 =1表示已经连好的组 =0表示还没有连的组
vector<Line>ways[5005];//动态数组ways[5][3]表示从点5出发的第(3+1)条边 ways[5][3].to表示这条从点5出发的边指向哪个点
int main()
{
	int n, m;
	cin >> n >> m;//n个结点 m个边
	for (int i = 1; i <= m; i++)
	{//输入处理
		int a, b, v;//两个端点和长度
		cin >> a >> b >> v;
		Line t1, t2;//由于是无相图,两个方向都可以到达,建立两个边 终点分别设置为对方
		t1.to = b; t2.to = a;
		t1.len = t2.len = v;
		ways[a].push_back(t1);//记录点a的情况:终点是b 长度是len
		ways[b].push_back(t2);
	}
	for (int i = 1; i <= n; i++)
	{//因为要选取最短距离 所以初始状态设置为无穷大
		distence[i] = 0x3f3f3f3f;
	}
	int ans = 0, cnt = 1, last = 1;//lsat:上一次最后一个连接的点 cnt:已连接点的数目 ans:长度和
	g[1] = 1;//从点1出发标记点1为已连接
	while (cnt < n)
	{//没连接到n-1个点就继续连
		for (int i = 0; i < ways[last].size(); i++)
		{//探索点last为起点的所有边
			int id = ways[last][i].to;//当前这个边指向的点
			if (g[id] == 0)
			{//如果点id未连接
				distence[id] = min(distence[id], ways[last][i].len);//更新last为起点 id为终点的权最小的边
			}	
		}
		int min_to = 0, min_dis = 0x3f3f3f3f;
		for (int i = 1; i <= n; i++)
		{
			if (distence[i] < min_dis && g[i] == 0)
			{
				min_dis = distence[i];
				min_to = i;
			}
		}
		last = min_to;
		g[last] = 1;
		ans += min_dis;
		cnt++;
	}
	cout << ans;
}

有向网的最短路径Dijkstra算法

基本策略:按最短路径长度递增的次序求得各条最短路径。
动画演示:
https://www.bilibili.com/video/BV1zz4y1m7Nq?spm_id_from=333.337.search-card.all.click 
基本思路:
1.每次从未标记的节点中选择距离出发点最近的节点,标记收录到最优路径的集合中
2.计算刚加入标记集合的 节点A的邻近节点B(不包含已经标记的)的距离
若 节点A到出发点的距离+节点A B的距离<节点B的边长就更新结点B距离出发点的距离和B的前驱结点
输入样例:
9 16
1 2 3 4 5 6 7 8 9
1 2 1
1 3 5
2 3 3
2 4 7
2 5 5
3 5 1
3 6 7
4 5 2
5 6 3
4 7 3
5 7 6
5 8 9
6 8 5
7 8 2
7 9 7
8 9 4
样例输入2:
9 14
1 2 3 4 5 6 7 8 9
1 2 4
1 8 8
2 8 11
2 3 8
3 4 7
3 6 4
3 9 2
4 5 9
4 6 14
5 6 10
6 7 2
7 9 6
7 8 1
8 9 7
输出:
0 4 12 19 28 16 18 8 14
NULL 1 2 3 4 3 6 1 3
输出解释:
1到i最短距离:0 4 12 19 28 16 18 8 14  i从1到9 例如 14表示从1到9的最短距离
1前驱 2前驱  3前驱 4前驱 5前驱 6前驱 7前驱 8前驱 9前驱
NULL  1      2     3     4     3     6       1     3 
例如我要找到结点1到结点5的最短路径:5 <- 4 <- 3 <- 2 <-1
结点1到结点9的最短路径:9 <- 3 <- 2 <- 1

#include<iostream>
using namespace std;
#define MAX 20
#define inf 99999
#include<iostream>
using namespace std;
#define ElemType char//顶点的数据类型
#define MAX 20 //最大顶点个数
typedef struct ArcNode
{
	int addressvex;//存储的是该顶点数组中的位置
	int weight;//权值
	struct ArcNode* next;
}ArcNode;//边表

typedef struct VNode
{
	ElemType vertex;//是哪一个顶点
	struct ArcNode* firstarc;
}VNode;//单个顶点

typedef struct
{
	VNode AdjList[MAX];//顶点构成的结构体数组
	int vexnum, arcnum;//顶点数和边数
}ALGraph;//图的结构定义

int LocateVex(ALGraph* G, ElemType c)
{//在顶点表中查找顶点的位置

	for (int i = 1; i <= G->vexnum; i++)
	{
		if (c == G->AdjList[i].vertex)return i;
	}
	cout << "没有找到点" << c << endl; return -1;
}

void CreateDG(ALGraph* G)
{//创建有向图
	//1.输入顶点数和边数
	cin >> G->vexnum >> G->arcnum;
	//2.顶点表数据填值,初始化顶点表指针域
	for (int i = 1; i <= G->vexnum; i++)
	{
		cin >> G->AdjList[i].vertex;
		G->AdjList[i].firstarc = NULL;
	}
	//3.输入边的信息构造邻接表 
	ElemType v1, v2;
	int n, m;//记住点在顶点表中的位置
	ArcNode* p;
	int w;
	for (int i = 1; i <= G->arcnum; i++)
	{
		cin >> v1 >> v2>>w;
		n = LocateVex(G, v1);
		m = LocateVex(G, v2);
		if (n == -1 || m == -1)
		{
			cout << "没有这个顶点!" << endl; return;
		}
		p = new ArcNode;
		p->addressvex = m;
		p->weight = w;
		p->next = G->AdjList[n].firstarc;//头插法
		G->AdjList[n].firstarc = p;
	}
}
int visited[MAX] , dst[MAX] , last[MAX];//visited[i]记录i是否被标记 dst[i]记录点i到出发点的路径长度 last[i]记录点i的前一个点
void Dijkstra(ALGraph G)
{//计算起点v1到终点v2的最短路径
	for (int i = 1; i <= G.vexnum; i++)
	{
		visited[i] = 0;
		dst[i] = inf;
	}
	dst[1] = 0;//起点到起点距离为0

	for (int i = 1; i <= G.vexnum; i++)
	{
		int min = inf;
		int minnum;
		for (int j = 1; j <= G.vexnum; j++)
		{//找到路径最短的顶点
			if (visited[j] == 0 && dst[j] < min) {
				min = dst[j];
				minnum = j;
			}
		}
		int v = minnum;//记录当前未标记的点集中 最短dst点编号
		visited[v] = 1;//设置已访问
		ArcNode* p = G.AdjList[v].firstarc;
		while (p != NULL)
		{//遍历该点的领接点,如果未访问且权值+距离<原距离 就更新当前距离 和前驱结点
			if (visited[p->addressvex] == 0 && p->weight + dst[v] < dst[p->addressvex])
			{
				dst[p->addressvex] = p->weight + dst[v];
				last[p->addressvex] = v;
			}
			p = p->next;
		}
	}
}
int main()
{
	ALGraph G;
	CreateDG(&G);
	Dijkstra(G);
	for (int i = 1; i <= G.vexnum; i++)
		if (dst[i] != inf)
			printf("%d ", dst[i]);
	cout << endl<<"NULL ";
	for (int i = 2; i <= G.vexnum; i++)
			cout<<last[i]<<" ";
	return 0;
}

有向无环图的拓扑排序

什么是拓扑排序?
动画讲解:
https://www.bilibili.com/video/BV1Hy4y1179t?spm_id_from=333.337.search-card.all.click
代码和思路讲解:
https://www.bilibili.com/video/BV11P4y1x76D?spm_id_from=333.337.search-card.all.click
拓扑排序(directed acyclic graph)可行的充要条件:给定的图是有向无环图
方法:用递归的深度优先搜索DFS
1.同时要检测环:如果DFS找到了正在进行的DFS的点,
则证明有环!因为如果x出边找到的点y的DFS还在进行,证明x就是
顺着y往下找到的,这就是环!
2.用一个数组记录每个点的状态:①还没遇到②DFS进行中③DFS已完成
3.每个DFS完成的点要排在最靠后的空位上,也就是所有移交排过的点的前面
4.最后要从每个点开始尝试DFS 保证给每个点都排过

输入样例:
5 6
1 2
2 3
3 4
4 5
2 4
2 5
输出样例:
1 2 3 4 5
输入样例2:
6 6
1 2 
1 6
2 3
6 3
3 5
5 4
输出样例2;
1 2 6 3 5 4
样例输入3:
6 7
1 2
1 6
2 3
6 3
3 5
5 4
4 3

#include<iostream>
#include<stack>
#define MAX 100
using namespace std;
struct EDGE
{
	int v1, v2;
	EDGE* next;
}epool[MAX];
struct NODE
{
	int mark;//0:没有遇到 1:已经完成DFS -1:正在DFS中
	EDGE* first;
}nodes[MAX];
int N, M, etop;//点数 边数 内存值位置
bool valid = true;//DFS过程中是否已经遇到环 遇到环变成FALSE
stack<int>ansstack; //拓扑排序存起来
void addedge(int v1, int v2)
{//添加边的情况
	epool[etop].v1 = v1;
	epool[etop].v2 = v2;
	epool[etop].next = nodes[v1].first;
	nodes[v1].first = &epool[etop];
	etop++;
}
void dfs(int id)
{
	if (nodes[id].mark == -1)
	{//正在DFS 表示存在环
		valid = false;
		return;
	}
	if (nodes[id].mark == 1)
	{//已经完成DFS 直接返回
		return;
	}
	//之前没有遇到
	nodes[id].mark = -1;//标记为正在DFS
	EDGE* e = nodes[id].first;
	while (e != NULL)
	{
		dfs(e->v2);
		e = e->next;
	}
	ansstack.push(id);
	nodes[id].mark = 1;//标记为已经完成DFS
}
int main()
{
	int x, y;
	cin >> N >> M;
	for (int i = 1; i <= M; i++)
	{//建有向图
		cin >> x >> y;
		addedge(x, y);
	}
	for (int i = 1; i <= N; i++)
	{//对每个点都进行dfs避免漏点
		dfs(i);
	}
	if (valid)
	{//valid==true 可以得到拓扑排序
		while (!ansstack.empty())
		{//输出拓扑排序
			cout << ansstack.top() << " ";
			ansstack.pop();
		}
		cout << endl;
	}
	else cout << "存在环,无法拓扑排序";
}

图的应用--走迷宫DFS

/*
从某一点出发,沿着一个方向往下试探
当找到目标位置,还需回溯,以便找到所有路径
再比较最短路径,比较盲目,效率没有BFS高。
DFS运用到了栈 
【问题描述】给定一个迷宫,求从起点到终点最短步长 
5 4
1 1 2 1
1 1 1 1
1 1 2 1
1 2 1 1
1 1 1 2
1 1 4 3
*/

#include<iostream>
using namespace std;
int m, n, p, q, Min=99999;
int a[100][100];//1表示空地 2表示障碍物
int v[100][100];//0表示未访问 1表示访问
int dx[4] = { 0,1,0,-1 }, dy[4] = {1,0,-1,0};//方向数组
void dfs(int x,int y,int step)
{

	if (x == p && y == q)
	{
		if (step < Min)
		{
			Min = step;
			return;//回溯
		}
	}
		//顺时针试探
		for (int k = 0; k <4; k++)
		{
			int tx, ty;
			tx = x + dx[k];
			ty = y + dy[k];
			if (a[tx][ty] == 1 && v[tx][ty] == 0)
			{
				v[tx][ty] = 1;
				dfs(tx, ty, step + 1);
				v[tx][ty] = 0;
			}
		}
//		//右
//		if (a[x][y + 1] == 1 && v[x][y + 1] == 0)//右边为空地且未访问
//		{
//			v[x][y + 1] = 1;//设置当前位置的右边为已访问
//			dfs(x, y + 1, step + 1);//再从右边这个点进行深度优先搜索
//			v[x][y + 1] = 0;//回溯后该右边的点重新设置为未访问
//		}
//		//下
//		if (a[x+1][y] == 1 && v[x+1][y] == 0)
//		{
//			v[x+1][y] = 1;
//			dfs(x+1, y , step + 1);
//			v[x+1][y] = 0;
//		}
//		//左
//			if (a[x][y-1 ] == 1 && v[x][y-1] == 0)
//		{
//			v[x][y-1] = 1;
//			dfs(x , y-1, step + 1);
//			v[x ][y-1] = 0;
//		}
//		//上
//			if (a[x - 1][y] == 1 && v[x - 1][y] == 0)
//			{
//				v[x - 1][y] = 1;
//				dfs(x - 1, y, step + 1);
//				v[x - 1][y] = 0;
//			}
	return;
}
int main()
{
	int startx,starty;
	cin >> m >> n;
	for (int i = 1; i <= m; i++)
	{
		for (int j = 1; j <= n; j++)
		{
			cin >> a[i][j];//1空地 2障碍物
		}
	}
	cin >> startx >> starty;
	cin>> p >> q;//输入起点终点坐标
	v[startx][starty] = 1;
	dfs(startx, starty, 0);
	cout << Min;
	return 0;
}

图的应用--走迷宫BFS


广度优先解决迷宫问题
求解思路:将队首结点可拓展的点入队,
如果没有可扩展的点,就将队首结点出队。
重复以上步骤,直到到达目标位置或者队列为空,
BFS搜索的结果一定是最短的。BFS运用了队列
[输入实例]
5 4 
1 1 2 1
1 1 1 1
1 1 2 1
1 2 1 1
1 1 1 1
1 1 4 3
[输出实例]7
*/

#include<queue>
#include<iostream>
using namespace std;
int a[100][100], v[100][100];
struct point{
	int x, y;
	int step;
};
queue<point>r;//申请队列
int dx[4] = { 0,1,0,-1 }, dy[4] = {1,0,-1,0};//方向数组右下左上

int main()
{
	//输入
	int n, m,startx,starty,p,q;
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			cin >> a[i][j];
		}
	}
	cin >> startx >> starty >> p >> q;
	//BFS广义搜索
	point start;
	start.x = startx;
	start.y = starty;
	start.step = 0;
	r.push(start);//将起点入队
	v[startx][starty] = 1;
	int flag;
	while (!r.empty())
	{
		int x = r.front().x;
		int y = r.front().y;
		if (x == p && y == q)
		{
			flag = 1;
			cout << r.front().step; break;
		}
		for (int k = 0; k < 4; k++)
		{
			int tx, ty;
			tx = x + dx[k];
			ty = y + dy[k];
			if (a[tx][ty] == 1 && v[tx][ty] == 0)
			{
				//入队
				point temp;
				temp.x = tx;
				temp.y=ty;
				temp.step = r.front().step + 1;
				r.push(temp);
				v[tx][ty] = 1;
			}
		}
		r.pop();//拓展完毕需要将队首元素出队
	}
	if(flag==0)
	{
		cout << "no answer!";
	}
	return 0;

}

图的应用--DFS 解决n皇后问题


这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。
并把它们以上面的序列方法输出,解按字典顺序排列。
请输出前 3个解。最后一行是解的总个数。
输入格式
一行一个正整数 nn,表示棋盘是 n  n×n 大小的。
输出格式
只有一个数字,表示解的总数。
输入输出样例
输入 
8
输出 
92


#include<iostream>
using namespace std;
const int N = 10;
int a[N];//a[i]表示第i行上的皇后放于a[i]列上
int cnt;
int n;
bool check(int x,int y)
{
	for (int i = 1; i <= x; i++)
	{
		if (a[i] == y)return false;
		if (i + a[i] == x + y)return false;
		if (i - a[i] == x - y)return false;
	}
	return true;
}
void dfs(int row)
{//表示第row皇后放在何处
	if (row == n + 1)
	{//产生了一组解
		for (int i = 1; i <= n; i++)
		{
			cout << a[i] << " ";
		}
		cout << endl;
		cnt++; return;
	}
	for (int i = 1; i <= n; i++)
	{
		if (check(row, i))
		{
			a[row] = i;
			dfs(row + 1);
			a[row] = 0;
		}
	}
}
int main()
{
	cin >> n;
	dfs(1);
	cout << cnt;//输出n皇后有多少组解
	return 0;
}

图的应用--DFS解决数独问题

【问题描述】
 数独是一种运用纸、笔进行演算的逻辑游戏。
 玩家需要根据9X9盘面上的已知数字,推理出所有剩余空格的数字,
数独问题的约束条件为:
l)数值范围仅限于l一9。
2)行中不允许重复数字。
3)列中不允许重复数字。
4)小九宫内不允许重复数字。
 【输入形式】
 输入为9X9的二维数组,每个数字均为0-9之间的数字,其中0表示该位置的数字为未知。
【输出形式】
 输出为9X9的二维数组,每个数字均为1-9之间的数字,满足
【样例输入】
0 0 3 5 0 0 0 0 2
0 0 8 6 0 0 0 0 0
0 7 0 0 0 0 1 0 0
0 1 0 0 0 0 6 0 0
0 5 0 0 1 0 0 7 0
0 0 6 9 0 0 0 3 0
0 0 9 0 0 0 0 5 0
0 0 0 0 0 9 7 0 0
6 0 0 0 0 8 9 0 0
【样例输出】
1 6 3 5 4 7 8 9 2
5 9 8 6 2 1 3 4 7
2 7 4 8 9 3 1 6 5
3 1 7 4 8 5 6 2 9
9 5 2 3 1 6 4 7 8
8 4 6 9 7 2 5 3 1
7 8 9 1 6 4 2 5 3
4 3 1 2 5 9 7 8 6
6 2 5 7 3 8 9 1 4

#include<iostream>
using namespace std;
int a[9][9];
 //检查是否符合约束条件
bool check(int x, int y, int id)
{
	for (int i = 0; i < 9; i++)
	{
		if (a[i][y] == id)
		{//检查该列
			return false;
		}
		if (a[x][i] == id)
		{//检查该行
			return false;
		}
	}
	//检查该元素所在的九宫格是否有重复元素
	for (int i = x / 3 * 3; i < x / 3 * 3 + 3; i++)
	{
		for (int j = y / 3 * 3; j < y / 3 * 3 + 3; j++)
		{
			if (a[i][j] == id)
			{
				return false;
			}
		}
	}
}
void dfs(int x, int y)
{
	//最终递归退出条件
	if (x == 8 && y == 9)
	{//检测到第八行最后一个元素的时候基本上已经可以确定了,因为只剩下第九行了
		for (int i = 0; i < 9; i++)
		{
			for (int j = 0; j < 9; j++)
			{
				cout << a[i][j]<<" ";
			}
			cout << endl;
		}
		return;//搜索完毕,输出完毕退出DFS深度搜索 
	}
	if (y == 9)
	{//检索到每一行的最后一个元素 下一个遍历的是下一行第一个元素
		y = 0;
		x++;
	}
	if (a[x][y] != 0)
	{//检测到非零元素(已经确定)直接跳过检索下一个
		dfs(x, y + 1); return;
	}
	for (int i = 1; i <= 9; i++)
	{//对当前位置检查看看符不符合约束条件
		if (check(x, y, i))
		{
			a[x][y] = i;//暂时确定该位置为i 
			dfs(x, y + 1);//检索下一个位置 
			a[x][y] = 0;//如果退出了就回溯,重新检查当前位置的i,直到得到正确结果 
		}
	}
}
int main()
{
	for (int i = 0; i < 9; i++)
	{
		for (int j = 0; j < 9; j++)
		{
			cin >> a[i][j];
		}
	}
	dfs(0, 0);
	return 0;
}

六、查找

1.顺序查找

顺序查找的改进
1)思想:顺序查找的改进:设置“哨兵”,就是待查值,放在查找方向的尽头处,免去了每一次比较后都要判断查找位置是否越界。
2)例子:
查找25
0  1  2  3  4  5  6  7  8  9
25 10 15 24 6  12 35 40 98 55
从索引9往前开始查找
时间复杂度O(n)

输入样例
9
10 15 24 6  12 35 40 98 55
25
输出样例:
no

输入样例2
9
10 15 24 6  12 35 40 98 55
15
输出样例:
Position:2

#include<iostream>
using namespace std;
int SeqSearch(int r[], int n, int k)
{
	int i = n;
	r[0] = k;//基本方法0位置空着 
	//优化版本设置哨兵,此处存放需要查找的值
	while (r[i] != k)i--;
	return i;//若i=0则未找到
}
int main()
{
	int a[10];
	int key;
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
	}
	cin >> key;
	int flag = SeqSearch(a, n, key);
	if(flag==0)cout<<"No"<<endl;
	else
	{
		cout << "Position:" << flag;
	}
}

2.二分查找(折半)

查找算法2.二分查找(折半查找)

1)思想:先找到那个有序序列的中间元素mid,然后拿它和要找的元素K进行比较,就可以初步判断K所在范围,
         既然查找范围已确定,自然该范围之外的元素就可以不用再查找了
二分查找有两个限制条件:
1.查找的数量只能是一个,不能是多个
2.查找的对象在逻辑上必须是有序的

2)例子:
查找25
n=10
0  1  2  3  4  5  6  7  8  9
6  10 12 15 24 25 35 40 55 98
初始化mid=(n-1)/2=4
mid=4 24<25 left=mid+1=5 right=9 
mid=7 40>25 right=mid-1=6 left=4
mid=5 25=25 找到输出5
时间复杂度O(lgn)

输入样例
10
6 10 12 15 24 25 35 40 55 98
25
输出样例:
5

#include<iostream>
using namespace std;
#define MAX 100
int HalfSearch(int r[], int n,int k)
{
	int mid;
	int left = 0;
	int right = n-1;
	while (left <= right)
	{
		mid = (left + right) / 2;//计算区间中间值
		if (r[mid] < k)left = mid + 1;
		else if (r[mid] > k)right = mid - 1;
		else
		{
			return mid;
		}
	}
	return 0;//没有找到
}
int main()
{
	int a[MAX];
	int key;
	int n;
	cin >> n;
	for (int i = 0; i < n; i++)
	{
		cin >> a[i];
	}
	cin >> key;
	int flag = HalfSearch(a, n, key);
	if(flag==0)cout<<"No"<<endl;
	else
	{
		cout << "Position:" << flag;
	}
}

3.插值查找

/*
查找算法3.插值查找(折半查找的优化)
相对于二分查找来说,查值查找更在乎数据的分布规律,换句话说,查值查找会根据数据的分布情况,
来决定要选择的拆分点middle,从而实现优化。
比如现在一个长度为100,分别存放着1到100的整数,我们要查找2,就不会从50这个点来拆分,而是会选择较小的数值。

条件:
(1)数据必须采用顺序存储结构;(2)数据必须有序。
原理:
与二分查找类似,区别在于拆分点的选取。
时间复杂度:
最好的情况下,即数据在某个范围内均匀分布时,时间复杂度为O(loglogn),可见优化不少。


输入样例
10
6 10 12 15 24 25 35 40 55 98
25
输出样例:
5

#include<iostream>
using namespace std;
#define MAX 100
int interSearch(int r[], int n,int k)
{
	int left = 0;
	int right = n - 1;
	while (left <= right)
	{
		int middle = left + (right - left) / (r[right] - r[left]) * (k - r[left]);
		if (r[middle] < k)left = middle + 1;
		else if (r[middle] > k)right = middle - 1;
		else
		{
			return middle;
		}
	}
	return 0;//没找到
}
int main()
{
	int a[MAX];
	int key;
	int n;
	cin >> n;
	for (int i = 0; i < n; i++)
	{
		cin >> a[i];
	}
	cin >> key;
	int flag = interSearch(a, n, key);
	if(flag==0)cout<<"No"<<endl;
	else
	{
		cout << "Position:" << flag;
	}
}

4.斐波拉契查找法

查找算法4.斐波拉契查找(折半查找的优化)
黄金分割点:是把一条线段分为两部分,使得一部分与全程之比等于另一部分跟这部分之比,比例近似于0.618,称为黄金分割点
斐波那契数列{1, 1, 2, 3, 5, 8 ... n, m, m + n}发现两个相邻数的比例,无限接近于0.618
斐波那契查找,依旧基于数组是有序数组,并使数组长度与斐波那契数组元素相匹配,之后类似于二分查找方式,
以斐波那契数组的数组特性f[k] = f[k - 1] + f[k - 2],对目标数组取中值middle = low+ f[k - 1] - 1后再进行分段查找
[举例]
比如有一个数组arr={1,2,3,4,5,6,7,8,9,10,11,12}要对他进行斐波那契查找,查找的值是10
首先,你得创建一个斐波那契数列出来我们定义为f[k],长度暂且定义为10吧那f={1,1,2,3,5,8,13,21,34,55},创建好了之后,我们再看原数组长度arr.length=12,根据斐波那契查找原则我们发现他的长度不等于斐波那契数列的某一个数值,所以我们要将数组的长度补至最近的斐波那契数,好,我们最近的值是13,所以我们复制最后一个元素至arr数组末尾(当然,数组长度是不能改变的,我们只能创建一个新的数组来复制arr数组的值并复制最后一个元素添加到末尾),好,新的数组元素就是{1,2,3,4,5,6,7,8,9,10,11,12,12}
接下来得找查找算法里的中间值了,斐波那契查找发就是讲原序列分为斐波那契数组里的连续的两个值,上面我们说了原序列长度为13,在斐波那契数列里找到f[k]=f[k-1]+f[k-2],即13=8+5,f[6]=f[5]+f[4],则中间值就是f[5]=8
找到中间值之后下来就是递归了,将目标值和中间值进行比较,如果目标值小于中间值,说明向左递归,将左边的部分继续分解为两个斐波那契数,以此类推直到找到目标数
输入样例
10
6 10 12 15 24 25 35 40 55 98
25
输出样例:
5

#include<iostream>
using namespace std;
#define MAX 100
void InitFBL(int n,int *a)
{
	a[0] = a[1] = 1;
	for (int i = 2; i <n; i++)
	{
		a[i] = a[i - 1] + a[i - 2];
	}
}
int FBLSearch(int r[], int n,int key)
{
	int left = 0;
	int right = n - 1;
	int FBL[MAX];
	InitFBL(n, FBL);//获取到斐波拉契数列
	int k = 0;//表示斐波拉契分割数值的下标
	//计算n位于斐波拉契数列的位置
	while (right> FBL[k]-1)
	{
		k++;
	}

	//将数组r[]扩展到(FBL[k]-1)的长度 构成新数组
	int *tmp = new int[FBL[k] - 1];
	//实际上需求使用r数组最后的数填充 temp
		//举例:
		//temp = {1,8, 10, 89, 1000, 1234, 0, 0}  => {1,8, 10, 89, 1000, 1234, 1234, 1234,}
	memcpy(tmp, r, n * sizeof(int));
	for (int i = right+1; i < FBL[k]-1; i++)
	{
		tmp[i] = r[right];
	}

	while (left <= right)
	{
		int middle = left + FBL[k- 1] - 1;
		if (tmp[middle] < key)
		{
			left = middle + 1;
			//为什么是k -=2
				//说明
				//1. 全部元素 = 前面的元素 + 后边元素
				//2. f[k] = f[k-1] + f[k-2]
				//3. 因为后面我们有f[k-2] 所以可以继续拆分 f[k-1] = f[k-3] + f[k-4]
				//4. 即在f[k-2] 的前面进行查找 k -=2
				//5. 即下次循环 mid = f[k - 1 - 2] - 1
			k -= 2;
		}
		else if (tmp[middle] > key)
		{
			right = middle - 1;
			//为甚是 k--
				//说明
				//1. 全部元素 = 前面的元素 + 后边元素
				//2. f[k] = f[k-1] + f[k-2]
				//因为 前面有 f[k-1]个元素,所以可以继续拆分 f[k-1] = f[k-2] + f[k-3]
				//即 在 f[k-1] 的前面继续查找 k--
				//即下次循环 mid = f[k-1-1]-1
			k--;
		}
		else
		{
			return middle;
		}
	}
	return 0;//没找到
}
int main()
{
	int a[MAX];
	int key;
	int n;
	cin >> n;
	for (int i = 0; i < n; i++)
	{
		cin >> a[i];
	}
	cin >> key;
	int flag = FBLSearch(a, n, key);
	if(flag==0)cout<<"No"<<endl;
	else
	{
		cout << "Position:" << flag;
	}
}

5.二叉排序树

/*输入样例
45 24 53 12 28 90 -1
28
28
输出样例
28
12 24 45 53 90
*/

#include<iostream>
using namespace std;
typedef struct BSTNode
{
	int data;
	BSTNode* lchild, * rchild;
}BSTNode,*BSTree;
//二叉排序树的递归查找
BSTree find(BSTree T, int key)
{
	if (!T || T->data == key)return T;
	else if (key < T->data) return find(T->lchild, key);
	else return find(T->rchild, key);
}
void insert(BSTree& T, int e)
{
	if (!T)
	{
		BSTree S = new BSTNode;
		S->data = e;
		S->lchild = S->rchild = NULL;
		T = S;
	}
	else if (e < T->data)insert(T->lchild, e);
	else if (e > T->data)insert(T->rchild, e);
}
void build(BSTree& T)
{
	T = NULL;
	int e;
	cin >> e;
	while (e != -1)
	{
		insert(T, e);
		cin >> e;
	}
}
void del(BSTree& T, int key)
{//从二叉排序树中删除关键字等于key的结点
	BSTree p = T;
	BSTree f = NULL;
	BSTree q=NULL, s=NULL;
	if (!T)return;//树空直接退出
	while (p)//查找
	{
		if (p->data == key)break;//找到
		f = p;//f为p的双亲
		if (p->data > key)p = p->lchild;//左子树中继续查找
		else	p = p->rchild;//右子树中继续查找
	}
	if (!p)return;//找不到需要删除的结点 直接退出
	//三种情况:p左右子树都不空,无左子树,无右子树
	if (p->lchild && p->rchild)
	{
		q = p;
		s = p->lchild;
		while (s->rchild)//在左子树中找到最右的结点
		{
			q = s; s = s->rchild;
		}
		p->data = s->data;//赋值给被删结点p然后删除s结点
		if (q != p)q->rchild = s->lchild;//重接q的右子树
		else q->lchild = s->lchild;//重接q的左子树
		delete s;
	}
	else
	{
		if (!p->rchild)//被删结点p无右子树,只需要重接其左子树
		{
			q = p;
			p = p->lchild;
		}
		else if (!p->lchild)//被删结点p无左子树,只需要重接其右子树
		{
			q = p;
			p = p->rchild;
		}
		if (!f)T = p;//被删结点为根结点
		else if (q == f->lchild)f->lchild = p;//挂接到f的左子树位置
		else f->rchild = p;//挂接到f的右子树位置
		delete q;
	}
}
//中序序列输出
void display(BSTree T)
{
	if (T != NULL)
	{
		if(T->lchild)display(T->lchild);
		cout << T->data<<" ";
		if(T->rchild)display(T->rchild);
	}
}
int main()
{
	BSTree T;
	BSTNode* s;
	int key;
	build(T);
	cin >> key;
	s=find(T, key);
	cout << s->data << endl;
	cin >> key;
	del(T, key);
	display(T);
}

6.平衡二叉树

/*输入样例
5
5 2 4 1 3
输出样例
5add in BTree
2add in BTree
4add in BTree
1add in BTree
3add in BTree
1 2 3 4 5
*/

#include<iostream>
using namespace std;
#define LH 1
#define EH 0
#define RH -1
typedef struct BTNode
{

	int data;
	int bf;
	struct BTNode* lchild, * rchild;
}BTNode,*BTree;
//以P为根的二叉排序树作右旋处理
void LL(BTree *p)
{
	BTree L;
	L = (*p)->lchild;//L指向P的左子树根节点
	(*p)->lchild = L->rchild;//L的右子树挂接为P的左子树
	L->rchild = *p;
	*p = L;//P指向新的根节点
}
//以O为根的二叉排序树作左旋处理
void RR(BTree* p)
{
	BTree R;
	R = (*p)->rchild;//R指向P的右子树根结点
	(*p)->rchild = R->lchild;//R的左子树挂接为P右子树
	R->lchild = (*p);
	*p = R;//P指向新的根节点
}
//以T为根节点的二叉树作左平衡旋转处理
void LR(BTree* T)
{
	BTree L, Lr;
	L = (*T)->lchild;//L指向T的左子树根节点
	switch (L->bf)
	{
		//检查T的左子树的平衡度,并作相应平衡处理
	case LH://新节点插入在T的左孩子的左子树上,要作单右旋处理
		(*T)->bf = L->bf = EH;
		LL(T);
		break;
	case RH://新节点插入在T的左孩子的右子树上,要作双旋处理
		Lr = L->rchild;
		switch (Lr->bf)//修改T及其左孩子的平衡因子
		{
		case LH:
			(*T)->bf = RH;
			L->bf = EH;
			break;
		case EH:
			(*T)->bf = L->bf = EH;
			break;
		case RH:
			(*T)->bf = EH;
			break;
		}
		Lr->bf = EH;
		RR(&(*T)->lchild);
		LL(T);
	}
}
//对以T为根节点的二叉树作右平衡旋转处理
void RL(BTree* T)
{
	BTree R, Rl;
	R = (*T)->rchild;
	switch (R->bf)
	{//检查T的右子树平衡度,并作相应平衡处理
	case RH://新节点插入在T的右孩子的左子树上作单右旋处理
			(*T)->bf = R->bf = EH;
			RR(T);
			break;
	case LH://新节点插入在T的右孩子的右子树上作单左旋处理
		Rl = R->lchild;
		switch (Rl->bf)
		{//修改T及其左孩子的平衡因子
		case RH:
			(*T)->bf = LH;
			R->bf = EH;
			break;
		case EH:
			(*T)->bf = R->bf = EH;
			break;
		case LH:
			(*T)->bf = EH;
			R->bf = RH;
			break;
		}
		Rl->bf = EH;
		LL(&(*T)->rchild);
		RR(T);
	}
}
//若在平衡的二叉排序树T中不存在和e有相同关键字的结点,则插入一个数据元素为e的新结点并返回1,否则返回0
//若因插入而使二叉排序树失去平衡,则做平衡旋转处理,变量taller反应T长高与否
int Insert(BTree* T, int e, int* taller)
{
	if (!*T)
	{
		//插入新节点,树长高,置taller为1
		*T = new BTNode;
		(*T)->data = e;
		(*T)->lchild = (*T)->rchild = NULL;
		(*T)->bf = EH;
		*taller = 1;
	}
	else
	{
		if (e == (*T)->data)
		{//树中已存在和e相同的关机键字的结点则不再插入
			*taller = 0;
			return 0;
		}
		if (e < (*T)->data)
		{
			//应该继续在左子树中搜索
			if (!Insert(&(*T)->lchild, e, taller))//未插入
				return 0;
			if (*taller)//已插入到T的左子树中并且左子树长高
			{
				switch ((*T)->bf)
				{
				case LH://原本左子树比右子树高,需要左平衡处理
					LR(T);
					*taller = 0;
					break;
				case EH://原本左右子树登高,现因左子树增高而树增高
					(*T)->bf = LH;
					*taller = 1;
					break;
				case RH://原本右子树比左子树高,现在左右子树登高
					(*T)->bf = EH;
					*taller = 0;
					break;
					break;
				}
			}
		}
		else
		{//应该继续在T的右子树进行搜索
			if (!Insert(&(*T)->rchild, e, taller))return 0;
			if (*taller)//已插入到T的右子树中且右子树长高
			{
				switch ((*T)->bf)
				{
				case LH://原本左子树比右子树高。,限你在左右子树等高
					(*T)->bf = EH;
					*taller = 0;
					break;
				case EH://原本左右子树等高,现因右子树增高而树增高
					(*T)->bf = RH;
					*taller = 1;
					break;
				case RH://原本右子树比左子树高,需要作右平衡处理
					RL(T);
					*taller = 0;
					break;
				}
			}
		}
	}
	return 1;
}
void display(BTree T)
{//中序输出
	if (T != NULL)
	{
		display(T->lchild);
		cout << T->data << " ";
		display(T->rchild);
	}
}
int main()
{
	BTree T = NULL;
	int taller;
	int n;
	int x;
	int j;
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		cin >> x;
		j = Insert(&T, x, &taller);
		if (j == 1)cout << x << "add in BTree" << endl;
		else cout << "repeat" << endl;
	}
	display(T);
	return 0;

}

7.B树

8.哈希查找

哈希查找:对数据关键字key,通过一个函数映射H(key)计算出数据在内存的位置
时间复杂度O(1)
哈希查找:
        m个数据连续存储空间,m为哈希表的大小
        H(Ki)的值为0到m-1之间
        实际存储的元素和哈希表大小的比值叫做负载因子(负载系数)
        负载因子越小,地址冲突的可能性越小,空间利用率越低
常用的哈希函数:
1.直接寻址法:H(key)=a*key+b
 举例:序列{100,200,330,520,600,815} 哈希函数H(key)=key/100+1
 将上述序列映射到{0,1,2,4,5,7}下标分量中,哈希表大小m=8 负载因子6/8=0.75
 2.除留余数法:H(key)=key mod p
 举例:序列{35,192,64,76,653} 哈希函数H(key)=key mod 7
 将上述序列映射到{0,3,1,5 ,6,2}下标分量中,哈希表大小m=7 负载因子0.86
 函数中p通常取大于元素个数n的最小素数 节省空间
 3.数据分析法:数据的关键字中如果由一些上位数据分布比较均匀,能区分不同的数据元素
 就可以将些位取出来作为数据元素的存储地址用
 4.平均取中法:关键字分布不均匀,将关键字平方,然后用直接定地或数据分析法获得哈希地址
 例如关键字136平方后18496取第2和3位得49作为哈希地址
 5.折叠法:当关键字位数比元素个数大得多,可以将其按照哈希表大小分割成若干段,
 将这些段相加得到的和作为哈希地址
 例如由100个数据元素,其中一个关键字为135763420875,以长度为2进行折叠并且相加:
 13+57+63+42+08+75=250作为哈希地址
 
 当地址冲突时候:重新计算哈希地址,如果计算后的地址仍然在原哈希表中,称为闭哈希表
 相反,如果冲突在原来哈希表外的另一个存储地址称为开哈希表
 解决地址冲突的方法:
 1.线性探测法(开哈希表):
 用(H(key)+i)%m重新定址,其中i为冲突的次数,m为哈希表大小
 2.二次探测法(闭哈希表):
 用(H(key)±i^2)%m ,其中当i为奇数取+ 偶数取-
 3.链地址法:


七、排序

1.冒泡排序

#include<iostream>
using namespace std;
void bubble_sort(int a[],int n)
{
    bool changeFlag=1;//浜ゆ崲鏍囧織
    for(int j=n-1;j>0;j--)
    {
        if(!changeFlag)break;
        changeFlag=false;
        for(int i=0;i<j;i++)
        {
            if(a[i]>a[i+1])
            {
                int tmp=a[i];
                a[i]=a[i+1];
                a[i+1]=tmp;
                changeFlag=true;
            }
        }

    }
}
int main()
{
    int a[10]={1,8,5,7,4,6,1,2,3};
    bubble_sort(a,9);
    for(int i=0;i<9;i++)
    cout<<a[i]<<" ";
    
}

2.希尔排序

#include<iostream>
using namespace std;
void bubble_sort(int a[],int n)
{
    bool changeFlag=1;//浜ゆ崲鏍囧織
    for(int j=n-1;j>0;j--)
    {
        if(!changeFlag)break;
        changeFlag=false;
        for(int i=0;i<j;i++)
        {
            if(a[i]>a[i+1])
            {
                int tmp=a[i];
                a[i]=a[i+1];
                a[i+1]=tmp;
                changeFlag=true;
            }
        }

    }
}
int main()
{
    int a[10]={1,8,5,7,4,6,1,2,3};
    bubble_sort(a,9);
    for(int i=0;i<9;i++)
    cout<<a[i]<<" ";
    
}

3.归并排序

#include<iostream>
using namespace std;
//两个有序序列合并
void merge(int a[], int low, int mid, int high)
{
    int i, j, k;//i:a[low到mid]的下标  j:a[mid+1到high]的下标
    int* c;
    c = new int[high - low + 1];
    i = low; j = mid + 1; k = 0;
    //两个有序序列的元素比较合并
    while ((i <= mid) && (j <= high))
    {
        if (a[i] <= a[j])
        {
            c[k] = a[i]; i++;
        }
        else {
            c[k] = a[j]; j++;
        }
        k++;
    }
    while (i <= mid)
    {//a序列中i未越界抄写剩余元素
        c[k] = a[i]; i++; k++;
    }
    while (j <= high)
    {//a序列中j未越界抄写剩余元素
        c[k] = a[j]; j++; k++;
    }
    for (i = 0; i < high - low + 1; i++)
    {
        a[i + low] = c[i];
    }
    delete[]c;
}

void mergeSort(int a[], int low, int high)
{
    int mid;
    if (low >= high)return;
    mid = (low + high) / 2;
    mergeSort(a, low, mid);
    mergeSort(a, mid + 1, high);
    merge(a, low, mid, high);
}
void mergeSort(int a[], int n)
{
    mergeSort(a, 0, n - 1);
}
int main()
{
    int a[10] = { 1,8,5,7,4,6,1,2,3 };
    mergeSort(a, 9);
    for (int i = 0; i < 9; i++)
        cout << a[i] << " ";
}

4.快速排序

#include<iostream>
using namespace std;
void quickSort(int a[],int start,int end)
{
    int i,j,hole;
    //序列中没有元素或只有一个元素递归结束
    if(end<=start)return;
    int temp=a[start];
    hole=start;
    i=start;//从左到右的搜索指针
    j=end;//从右到左的搜索指针
    
    while(i<j){
    //从j位置开始从后往前找到第一个小于等于temp的值
    while((j>i)&&(a[j]>=temp))j--;
    if(j==i)break;
    a[hole]=a[j];
    hole=j;
    //从i位置开始从前往后找到第一个大于等于temp的值
    while((i<j)&&(a[i]<temp))i++;
    if(j==i)break;
    a[hole]=a[i];
    hole=i;
    }
    a[hole]=temp;

    //对标杆位置左边的序列用同样的方法
    quickSort(a,start,hole-1);
    //对标杆位置右边的序列用同样的方法
    quickSort(a,hole+1,end);
}
void quickSort(int a[],int n)
{
    quickSort(a,0,n-1);
}
int main()
{
     int a[10] = { 1,8,5,7,4,6,1,2,3 };
    quickSort(a, 9);
    for (int i = 0; i < 9; i++)
        cout << a[i] << " ";
}

5.选择排序

#include<iostream>
using namespace std;
void selecSort(int a[],int n)
{
    int i,j,minIndex;
    int temp;
    //为每个位置找到合适的数据
    for(int i=0;i<n;i++)
    {
        //为位置i找到合适的数据
        minIndex=i;
        for(j=i+1;j<n;j++)
        {
            if(a[j]<a[minIndex])
                minIndex=j;
        }
        //将minIndex位置上的数据和位置i上的数据交换
        if(minIndex==i)continue;
        temp=a[i];
        a[i]=a[minIndex];
        a[minIndex]=temp;
    }
}
int main()
{
     int a[10] = { 1,8,5,7,4,6,1,2,3 };
    selecSort(a, 9);
    for (int i = 0; i < 9; i++)
        cout << a[i] << " ";
}

6.堆排序

#include<iostream>
using namespace std;
void adjust(int a[],int n,int i)
{//对数量为n的数组a,假设根为0下标的元素
//调整下标为i的元素,使得以i为根的二叉树为一个大顶堆
int maxChild;
int temp;
while(1)
{
    maxChild=2*i+1;//i的左子下标
    
    if(maxChild>n-1)return;//没有左孩子 右孩子 
	  
    if(maxChild+1<=n-1)//i还有右子
    {
        if(a[maxChild+1]>=a[maxChild])//右孩子大于等于左孩子
        maxChild++;//右孩子最大
    }
    if(a[i]>a[maxChild])return;//最大孩子小于父节点 不需要调整
    //最大孩子大于等于父节点,父结点向下调整
    temp=a[i];
    a[i]=a[maxChild];
    a[maxChild]=temp;
    i=maxChild;//继续向下调整
}
}
void heapSort(int a[],int n)
{
    int i,j;
    int temp;
    //从倒数第一个非叶子结点开始调整,首次建立大顶堆
    for(i=(n-1)/2;i>=0;i--) adjust(a,n,i);
    //换大项,逐次减少参与的元素,重新调整为大顶堆
    for(j=n-1;j>=1;j--)
    {
        //大顶和第i个位置的元素交换
        temp=a[0];
        a[0]=a[j];
        a[j]=temp;

        //调整第0个元素
        adjust(a,j,0);
    }
}
int main()
{
     int a[10] = { 1,8,5,7,4,6,1,2,3 };
    heapSort(a, 9);
    for (int i = 0; i < 9; i++)
        cout << a[i] << " ";
}

总结

作者(黎爬爬)画了很长时间整理和试验,制作和整理不易,如有错误,还望海涵。

以上代码和思路分享仅供参考。

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涡巻ナルト人≥サス

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值