数据结构笔记

线性表

线性表定义
零个或多个数据元素的有限序列
线性表有序序列,数据元素具有相同的特性,线性表可以有无性

线性表特点
1.有序性
数据元素之间是(一对一)的关系
2.有限性
线性表的元素个数是有限的
ps 零个数据元素线性表叫空表

线性表存储结构
有顺序结构和链式存储结构
前者是 顺序表,后者是链表

1.顺序表

这里看看就好了,主要还是链表部分吧。。。。(因为大部分我都是直接上模板的,没怎么看过,应该不难吧,如果有bug,欢迎来打脸=_=||)
在这里插入图片描述

定义数据元素和顺序表

const int MAX_SIZE=100;
//定义数据元素 
//自定义一个新数据类型 
//其实就是int,double等等的合集 
typedef struct{
	int id1;
	char *name;
}element_type;
//定义顺序表结构 
typedef struct{
	element_type datas[MAX_SIZE];//顺序表中的数据元素集合 
	int  length;//当前顺序表中的元素个数 
}seqlist;

初始化顺序表以及插入操作

先说插入操作,因为你要初始化顺序表,其实就是把数据元素一个一个按顺序插入到顺序表中

//参数介绍
//seqlist 顺序表
//index 要插入的下标
//element_type 要插入的元素

void insertelement (seqlist * seqlist1,int index ,element_type element)
{
	if(seqlist1->length + 1 >= MAX_SIZE)
	{
		printf("数组已满,无法插入\n");
		return ; 
	}
	if(index < 0 || index > MAX_SIZE - 1)
	{
		printf("插入下标错误,无法插入\n");
		return ;
	}
	if(index > seqlist1 -> length)
	{
		printf("插入的下标超过数组长度-1,无法插入\n");
		return ; 
	}
	//下面正式插入模板
	for(int i = seqlist1 -> length-1;i >= index ;i--)
	{
		seqlist1->datas[i+1] = seqlist1 -> datas[i];
	} 
	seqlist1 -> datas[index] = element;
	//注意总长度加一 
	seqlist1 -> length++;
}

然后是初始化就很简单了,唯一的注意点就是要初始化长度为0,很重要!

//参数介绍
//seqlist 要初始化的顺序表
//element_type 初始化时要添加的元素内容数组
//length 初始化时要添加的元素个数

void initlist(seqlist * seqlist1,element_type * elemarray ,int length)
{
	if(length > MAX_SIZE)
	{
		printf("超出最大容量,无法初始化\n");
		return ; 
	}
	seqlist -> length =0;//注意点 
	for(int i=0;i<length;i++)
	{
		insertelement(seqlist1 , i ,elemarray[i]);
	}
} 

删除操作和寻找操作

element_type * getelement (seqlist * seqlist1 ,int index)
{
	if(index < 0 || index > MAX_SIZE - 1)
	{
		printf("下标越界,无法寻找\n");
		return NULL;
	}
	element_type * element;
	element = &seqlist1->datas[index];
} 

element_type * delete_element(seqlist * seqlist1,int index);
{
	if(index < 0 || index > MAX_SIZE - 1)
	{
		printf("下标越界,无法删除\n");
		return NULL; 
	}
	//找到要删除元素 
	element_type * delelement =(element_type*)malloc(sizeof(element_type));
	 delelment = getelement(seqlist1 ,index);
	
	for(int i= index; i < seqlist1->length-1;i++)
	{
		seqlist1->datas[i] = seqlist1 -> datas[i+1];
	}
	seqlist1->length--;
	return delelement ;//记得free 
}

其他小操作

int getlenth(seqlist * seqlist1)
{
	if(seqlist1==NULL)
	return 0; 
	return seqlist1->length;
}//找到顺序表长度

bool isempty(seqlist * seqlist1)
{
	return getlenth(seqlist1)==0;
}//判断是否为空

void clearlist(seqlist * seqlist1)
{
	if(seqlist1 ==NULL) return ;
	seqlist1 -> length = 0;
}//清除线性表

2.单链表

这里说下可能会恶心的点
1.原本我们短学期之前做的head里面是有数据的,而下面的代码中,head的数据域中是空的,也就是我们只用它的指针部分(因为这个,我按照以前的思维一直删除,插入的时候位置总是差一个,也不知道问题)
2.下面有一个参数是(node ** l)
这个就是(指针l的地址,应该理解理解就行不难,主要还是第一个问题)

定义链表

typedef struct{
	int id1;
	char *name;
}element_type;
//在复习一遍自定义,当然像下面一样不用也行
typedef struct node{
	//int id1;
	//char * name;
	element_type data; 
 	struct node * next;//指向下个结点 
}node;

在这里插入图片描述

创建操作

void create(node ** l,int n,element_type dataarray[])
{
	node *p1, *p2;
	int i;
	*l = (node*) malloc (sizeof(node));
	p2 = *l;
	for(i=0 ;i<n;i++)
	{
		p1=(node*) malloc (sizeof(node));
		p1->data = dataarray[i];
		p2->next = p1;
		//p1->next=NULL;
  		p2=p1;  
	}
	p2->next = NULL;
}

插入

void insertlinklist(node ** l,int i,element_type e)
{
	int j=1;
	node *p,*p1;
	p = *l;
	while(p && j<i)//p就是 p!=NULL
	{
		p = p->next;
		j++;
	} 
	
	if(!p || j>i)
	{
		//printf("错误\n");
		return ;
	} 
	p1 = (node *)malloc(sizeof(node));
	p1->data = e;
	p1->next = p->next;
	p->next  = p1;
}

删除

void deletelinklist(node ** l,int i,element_type *e)
{
	int j=1;
	node *p,*p1;
	p = *l;
	while(p->next && j<i)
	{
		p = p->next;
		j++;
	} 
	if(!(p->next)||j>i)
	{
		return ;
	}
	p1 = p->next;
	p->next =p1->next;
	*e = p1->data; //这就是删除的元素
	free(p1); 
}

//整体删除
void clearlist(node ** l)
{
	node* p,*p1;
	p = (*l)->next;
	while(p)
	{
		p1 = p->next;
		free(p);
		p = p1;
	}
	(*l)->next = NULL;
}

查询

void getelem(node **l,int i,element_type *e)//查询 
{
	int j=1;
	node * p;
	p = (*l)->next;
	j=1;
	while(p && j<i)
	{
		p=p->next;
		j++;
	}
	if(!p || j>i)
	{
		//printf("错误\n"); 
		return ;
	}
	*e = p->data;
	return ;
}

输出

void print(node ** l)// 输出 
{
	node*temp=(*l)->next;
	while(temp!=NULL)
	{
		printf("%d\t%s\n",temp->data.id1,temp->data.name);
		temp = temp->next; 
	}	
} 

小小总结一下(水文章)
如果线性表要频繁查找,很少插入和删除,那么顺序表更佳,否则就用链表;
如果元素的个数变化较大或不清楚时,用链表更好;


3.双向链表

双向链表支持两个方向,每个节点有一个后继指针next指向后面的节点,还有一个前驱指针指向前面的节点。
在这里插入图片描述

typedef struct {
	int id1;
	//char* name;
}element_type;
typedef struct node {
	element_type data;
	struct node* prior;
	struct node* next;
}node;
void init(node** l,int n, element_type dataarray[])
{
	node* p, * q;
	int i;
	*l = (node*)malloc(sizeof(node));
	if (!(*l))
		return;
	(*l)->next = (*l)->prior = NULL;
	p = (*l);
	for (i = 0; i < n; i++)
	{
		q = (node*)malloc(sizeof(node));
		if (!q)
			return;
		q->data = dataarray[i];
		q->prior = p;
		p->next = q;
		p = q;
	}
	p->next = NULL;
	//双向循环
	//p->next =(*l)->next;
	//(*l)->next->prior = p;
}
void insert(node** l, int i, element_type e)
{
	int j = 1;
	node* p, * s;
	p = *l;
	while (p && j < i)//p就是 p!=NULL
	{
		p = p->next;
		j++;
	}
	if (!p || j > i)
	{
		return;
	}
	s = (node*)malloc(sizeof(node));
	s->data = e;
	s->prior = p;
	if (!p->next)
	{
		s->next = NULL;
	}
	else
	{
		p->next->prior = s;
		s->next = p->next;
	}
	p->next = s;
}
void delete1(node** l, int i)
{
	int j = 1;
	node* p;
	p = (*l)->next;
	while (p && j < i)
	{
		p = p->next;
		j++;
	}
	if (!(p) || j > i)
	{
		return;
	}

	if (p->next == NULL)
	{
		p->prior->next = p->next;
	}
	else
	{
		p->prior->next = p->next;
		p->next->prior = p->prior;
	}
	free(p);
}

4.循环链表

对于单链表而言,最后一个结点指针域是空指针,如果将该链表头指针置入该指针域,则使得链表头尾结点相连,这就构成了单循环链表。和单链表的操作基本相同,只是把最后一个结点的指针域指向头指针而已,其他没有变化。

定义:
栈(stack) 是一个后进先出的线性表(LIFO),他要求只在表尾进行删除和插入操作
插入(push),即进栈
删除(pop),即出栈
栈顶(top)
栈底(bottom)

顺序结构

typedef struct {
	int id1;
	//char* name;
}element_type;
typedef struct
{
	element_type* base;//栈底的指针变量
	element_type* top;//栈顶的指针变量
	int stacksize;//当前可用最大容量
}stack;
void initstack(stack* s)//创建
{
	s->base = (element_type*)malloc(100 * sizeof(element_type));
	if (!s->base)
		exit(0);//申请失败
	s->top = s->base;//开始栈底就是栈顶
	s->stacksize = 100;//这里可以用变量,上面也要改
}
void push(stack* s, element_type e)
{
	if (s->top - s->base >= s->stacksize)//如果栈大小不够要增加容量
	{
		s->base = (element_type*)realloc(s->base, (s->stacksize + 10));//10可以变量代替更好
			if (!s->base)
				exit(0);
		s->top = s->base + s->stacksize;
		s->stacksize = s->stacksize + 10;//10变量代替
	}
	*(s->top) = e;
	s->top++;
}
void pop(stack *s,element_type *e)
{
	if (s->top == s->base)//栈空
		return;
	*e = *--(s->top);//--i和i--可以复习一下
}
//其他小操作
void clear(stack* s)
{
	s->top = s->base;//这里申请的空间没有释放
}
void destroy(stack* s)//相当于要重新创建了
{
	int i, len;
	len = s->stacksize;
	for (i = 0; i < len; i++)
	{
		free(s->base);
		s->base++;
	}
	s->base = s->top = NULL;
	s->stacksize = 0;
}
int len(stack s)//当前容量
{
	return(s.top - s.base);//指针之间可以相减不能相加
}

链式结构

(好像不用学=。=,只是一个知识点)
了解了解

typedef struct {
	int id1;
	//char* name;
}element_type;
typedef struct stack
{
	element_type data;
	struct stack* next;
}stack;
typedef struct linkstack
{
	stack* top;
	int count;//计数器
};
void push(linkstack* s, element_type e)
{
	stack* p = (stack*)malloc(sizeof(stack));
	p->data = e;
	p->next = s->top;
	s->top = p;
	s->count++;
}
void pop(linkstack *s,element_type *e)
{
	stack* p;
	if (s->count==0)
		return;//是否为空
	*e = s->top->data;
	p = s->top;
	s->top = s->top->next;
	free(p);
	s->count--;
}

队列

定义:
队列(queue)是只允许在一端进行插入,在另一端进行删除操作的线性表,和栈不同的是,队列先进先出(FIFO);

顺序队列

参考循环队列

#define MAXSIZE 100
typedef struct {
	int id1;
	//char* name;
}element_type;
typedef struct node {
	element_type* base;//用数组也行
	int front, rear;
}node;
void init(node* q)
{
	q->base = (element_type*)malloc(sizeof(element_type) * MAXSIZE);
	if (!q->base)
		exit(0);
	q->front = q->rear = 0;
}
void push(node* q,element_type e)
{
	if ((q->rear + 1) % MAXSIZE == q->front)
	{
		return;//队列已满
	}
	q->base[q->rear] = e;
	q->rear = (q->rear + 1) % MAXSIZE;
}
void pop(node* q)
{
	if (q->front == q->rear)
		return;//队列为空
	q->front = (q->front + 1) % MAXSIZE;
}

链式队列

typedef struct {
	int id1;
	//char* name;
}element_type;
typedef struct node {
	element_type data;
	struct node* next;
}node;
typedef struct {
	node* front,* rear;//队列头和尾指针
}linkqueue;
void initqueue(linkqueue* q)
{
	q->front = q->rear = (node*)malloc(sizeof(node));
	if (!q->front)
		exit(0);
	q->front->next = NULL;
}
void push(linkqueue* q, element_type e)
{
	node* p;
	p = (node*)malloc(sizeof(node));
	if (p == NULL)
		exit(0);
	p->data = e;
	p->next = NULL;
	q->rear->next = p;
	q->rear = p;
}
void pop(linkqueue* q, element_type* e)
{
	node* p;
	if (q->front == q->rear)
		return;
	p = q->front->next;
	*e = p->data;
	q->front->next = p->next;
	if (q->rear == p)
		q->rear = q->front;
	free(p);
}
void dextroy(linkqueue* q)
{
	while (q->front)
	{
		q->rear = q->front->next;
		free(q->front);
		q->front = q->rear;
	}
}

定义
树(tree)是n(>=0)个结点的有限集。当n=0是为空树。
在任意一棵非空树中:
1.有且只有一个称为根(root)的结点。
2.当n>1时,其余结点可分为m(m>0)个互不相交的有限集,每一个集合本身也是一棵树,称为根的子树。

结点拥有的子树数称为结点的度,树的度取树内的各结点的度的最大值
1.度为0的结点称为叶结点(leaf)或终端结点
2.度不为0的结点称为分支结点或非终端结点,除根结点外,分支结点也称为内部结点

树中结点的最大层次称为树的深度(depth)或高度
定义代码

#define MAXSIZE 100
typedef struct {
	int id1;
	//char* name;
}element_type;
//孩子结点
typedef struct node {
	int child;//孩子结点的下标
	struct node* next;//指向下一个孩子
}node;
//表头结构
typedef struct
{
	element_type data;
	int parent;//标记父亲的位置
	node** child;//指向孩子的指针
}tree;
typedef struct
{
	tree nodes[MAXSIZE];//结点数组
	int r, n;//根的位置和结点数
}tree;

二叉树

二叉树(binary tree)每个结点最多(不是一定)有两个子树,所以二叉树中不存在度大于2的结点。
基本形态
1.空二叉树
2.只有一个根结点
3.根结点只有左子树
4.根结点只有右子树
5.根结点既有左子树也有右子树

特殊二叉树

斜树
每个结点只有左儿子(左斜树)或者右儿子(右斜树)
满二叉树
所有的分支节点都有左子树和右子树,并且所有叶子在同一层上,这样的二叉树称为满二叉树。
1.叶子只能出现在最下一层
2.非叶子结点的度一定是2
3.在同样深度的二叉树中,满二叉树的结点个数一定是最多,同时叶子也是最多
完全二叉树
设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
1.叶子结点只能出现在最下两层
2.最下层的叶子一定集中在左部连续位置
3.倒数第二层若有叶子结点,一定都在右边连续位置
4.如果结点的度为1,那么该结点只有左儿子
5.同样结点树的二叉树,完全二叉树的深度最小

ps:完全二叉树不一定是满二叉树,满二叉树一定是完全二叉树

性质

1.在二叉树的第i层上至多有2i+1个结点(i>=1)
2.深度为k的二叉树至多有2k-1个结点(k>=1)
3.对任意一棵二叉树,如果其终端结点树为 n 0 n_0 n0,度为2
的结点树为 n 2 n_2 n2,则 n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1
4.具有n个结点的完全二叉树的深度为 ⌊ log ⁡ 2 n ⌋ \left \lfloor \log_2n \right \rfloor log2n+1(向下取整)
5.如果对一个有n个结点的完全二叉树(深度 ⌊ log ⁡ 2 n ⌋ \left \lfloor \log_2n \right \rfloor log2n+1)的结点按层序编号,对任意结点i(1<=i<=n)有以下性质;
(1)如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则双亲的结点是 ⌊ i 2 ⌋ \left \lfloor \frac i2 \right \rfloor 2i
(2)如果2i>n,则结点i无左儿子(结点i就是叶子结点);否则左儿子为结点2i
(3)如果2i+1>n,则结点i无右儿子;否则右孩子是结点2i+1

可实行代码

定义

typedef struct {
	int id1;
	char* name;
}element_type;
typedef struct node
{
	element_type data;
	struct node* left, * right;
}node;
//创建
void init(node** t)
{
	element_type e;
	scanf("%d%s", &e.id1,e.name);
	if(条件)//递归结束条件
	*t = NULL;
	else
	{
		*t = (node*)malloc(sizeof(node));
		(*t)->data = e;
		init(&(*t)->left);
		init(&(*t)->right);
	}
	//当然用数组也可以,这个好像输入的时候麻烦
}

遍历
从根结点出发,按某种次序依次访问二叉树中的所有结点,每个结点被访问一次且仅被访问一次
1.前序遍历,若二叉树为空;则空操作返回;
否则先访问根结点,然后前序遍历左子树,在前序遍历右子树
在这里插入图片描述
2.中序遍历,若二叉树为空;则空操作返回;
否则遍历最左的左子树,然后访问根结点,最后遍历右子树
在这里插入图片描述

3.后序遍历,若二叉树为空;则空操作返回;
否则从左到右先叶子后结点的方式遍历访问左右子树,最后访问根结点
在这里插入图片描述
4.层序遍历,若二叉树为空;则空操作返回;
否则从根结点,从上到下逐层访问,同一层从左往右遍历
其实就是算法中的bfs要用到队列

void visit(element_type e)
{
	printf("id:%d\tname:%s\n", e.id1,e.name);
}
void search1(node* t)//前序遍历
{
	if (t)//不为空就可以访问
	{
		visit(t->data);
		search1(t->left);
		search1(t->right);
	}
}
void search2(node* t)//中序遍历
{
	if (t)//不为空就可以访问
	{
		search2(t->left);
		visit(t->data);
		search2(t->right);
	}
}
void search3(node* t)//后序遍历
{
	if (t)//不为空就可以访问
	{
		search3(t->left);
		search3(t->right);
		visit(t->data);
	}
}

线索二叉树

充分利用二叉链表中的空链域,将遍历过程中结点的前驱、后继信息保存下来。
1.若结点有左子树,则其 LChild 域指向其 左孩子,否则 LChild 域指向其 前驱结点。
2.若结点有右子树,则其 RChild 域指向其 右孩子,否则 RChild 域指向其 后继结点。
在这里插入图片描述

typedef struct node
{
	element_type data;
	struct node* left, * right;
	//区别线索化,true=1表示前驱后继,false=0表示左右孩子指针;
	bool ltag, rtag;
}node;

void InThread(node* p, node*& pre)//建立中序线索二叉树
{
	if (p != NULL)
	{
		InThread(p->left, pre);	// 递归,左子树线索化
		if (p->left == NULL)		// 建立当前结点的前驱线索
		{
			p->left = pre;
			p->ltag = true;
		}
		if (pre != NULL && pre->right == NULL)	//建立前驱结点的后继线索
		{
			pre->right = p;
			pre->rtag = true;
		}
		pre = p;
		InThread(p->right, pre);	// 递归,右子树线索化
	}
}
void createInThread(node* root)
{
	node* pre = NULL;	//	前驱结点指针
	if (root != NULL)
	{
		InThread(root, pre);
		pre->right = NULL;	// 处理最后一个结点的后继
		pre->rtag = true;
	}
}
//中序遍历
node* First(node* p)	//	找到最左下的结点
{
	while (p->ltag == 0)
		p = p->left;
	return p;
}

node* Next(node* p)
{
	if (p->rtag == 0)	//	如果p存在右孩子,则p的后继结点是p的右子树中的最左下结点
		return First(p->right);
	return p->right;
}

void Inorder(node* root)
{
	for (node* p = First(root); p != NULL; p = Next(p))
		visit(p->data);
}

转换

树转换到二叉树
1.将每一个结点与他的兄弟结点之间连一条线。
2.对每一个双亲结点,只保留它与第一个子结点的连线,删除与其余结点的连线。
3.整理,左右摆齐。
在这里插入图片描述

森林转换到二叉树
1.把每棵树按照上面的方式转换为二叉树。
2.第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子,用线连接起来。
在这里插入图片描述
二叉树转换到树
1.加线。若某结点X的左孩子结点存在,则将这个左孩子的右孩子结点、右孩子的右孩子结点、右孩子的右孩子的右孩子结点…,都作为结点X的孩子。将结点X与这些右孩子结点用线连接起来。
2.去线。删除原二叉树中所有结点与其右孩子结点的连线。
3.层次调整。
在这里插入图片描述
二叉树转换为森林
备注:假如一棵二叉树的根节点有右孩子,则这棵二叉树能够转换为森林,否则将转换为一棵树。
1.从根节点开始,若右孩子存在,则把与右孩子结点的连线删除。再查看分离后的二叉树,若其根节点的右孩子存在,则连线删除…。直到所有这些根节点与右孩子的连线都删除为止。
2.将每棵分离后的二叉树转换为树。
在这里插入图片描述

定义
图(graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中的顶点的集合,E是图G中边的集合。

注意点:
1.线性表可以没有元素,称为空表,树中没有结点,叫做空树,而图结构强调顶点集合V要有穷非空
2.线性表中,相邻的数据元素之间有线性关系,数据结构中,相邻两层的结点具有层次关系,而图结构中,任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边来表示,边集可以是空的。

无向边:若顶点 v i v_i vi v j v_j vj之间的边没有方向,则称这条边为无向边(edge),用无序偶( v i , v j v_i,v_j vi,vj)来表示。

有向边:若顶点 v i v_i vi v j v_j vj之间的边方向,则称这条边为有向边,也称为弧(arc),用无序偶< v i , v j v_i,v_j vi,vj>来表示, v i v_i vi称为弧尾, v j v_j vj称为弧头。

简单图:在图结构中,若不存在顶点到其自身的边,且同一条边不重复出现,则称这样的图为简单图。

无向完全图:在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图。含有n个顶点的无向完全图有n*(n-1)/2条边。

有向完全图:在有向图中,如果任意两个顶点之间都存在方向互为相反的两条弧,则称该图为有向完全图。含有n个顶点的无向完全图有n*(n-1)条边。

稀疏图和稠密图:这里的稀疏和稠密是模糊的概念,都是相对而言的,通常认为边或弧数小于n* log ⁡ n \log n logn(n是顶点的个数)的图称为稀疏图,反之称为稠密图。

有些图的边或者弧带有与它相关的数字,这种与图的边或弧相关的数叫做权(weight),带权的图通常被称为网(networt)

假设有两个图G1=(V1,E1)和G2=(V2,E2),如果V2 ⊆ \subseteq V1,E2 ⊆ \subseteq E1,则称G2为G1的子图(subgraph)

图的顶点和边之间的关系:
1.对于无向图G=(V,E),如果边(V1,V2) ⊆ \subseteq E,则称顶点V1和V2互为邻接点(adjacent),即V1和V2相邻接。边(V1,V2)依附(incident)于顶点V1和V2,或者说边(V1,V2)与顶点V1和V2相关联
顶点V的(degree)是和V相关联的边的数目,记为TD(V)。
2.对于有向图G=(V,E),如果边<V1,V2> ⊆ \subseteq E,则称顶点V1邻接到顶点V2,顶点V2邻接自顶点V1。
以顶点V为头的弧的数目称为V的入度(indegree),记为ID(V);以顶点V为尾的弧的数目称为V的出度(outdegree),记为OD(V),因此顶点V的度为TD(V)=ID(V)+ OD(V)。

无向图G=(V,E)中从顶点V1到顶点V2的路径(path)
路径的长度是路径上的边或弧的数目。
第一个顶点到最后一个顶点相同的路径称为回路或环(cycle)

序列中顶点不重复出现的路径称为简单路径,除了第一个顶点和最后一个顶点之外,其余顶点不重复出现的回路,称为简单回路或简单环

无向图G中,如果从顶点V1到顶点V2有路径,则称V1和V2是连通的,如果对于图中任意两个顶点 V i , V j V_i,V_j Vi,Vj是连通的,则称G是连通图(connectedgraph)
有向图G中,如果对于每一对顶点 V i , V j V_i,V_j Vi,Vj都存在路径,则称G是强连通图

有向图中的极大强连通子图称为有向图的强连通分量

图的生成树:一个连通图的生成树是一个极小的连通子图,它含有图中全部的n个顶点,但只有足以构成一棵数的n-1条边。

如果一个有向图恰有一个顶点入度为0,其余顶点的入度为1,则是一棵有向树。

代码

1.邻接矩阵(adjacency matrix)
两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)存储图中的边或弧的信息。

#define Maxvex 100
#define Infinity 65535
typedef struct
{
	char vexs[Maxvex]; //顶点表
	int arc[Maxvex][Maxvex]; //邻接矩阵,可看作边表
	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; i++)
			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);
		G->arc[i][j] = w;
		G->arc[j][i] = G->arc[i][j];
	}
}

2.邻接图

//结点的定义
#define MaxVex 100
typedef struct EdgeNode //边表结点
{
	int adjvex; //邻接点域,存储邻接顶点对应的下标
	int weight; //用于存储权值,对于非网图可以不需要
	struct EdgeNode *next; //链域,指向下一个邻接点
}EdgeNode;
typedef struct VertexNode  //顶点表结点
{
	char data;  //顶点域,存储顶点信息
	EdgeNode *firstedge; //边表头指针
}VertexNode,AdjList[MaxVex];
 
typedef struct
{
	AdjList adjList;
	int numVertexes,numEdges; //图中当前顶点数和边数
}GraphAdjList;
//建立无向图的邻接表结构
void CreateALGraph(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(&G->adjList[i].data); //输入顶点信息
		G->adjList[i].firstedge = NULL; //将边表置为空表
	}
	for(k=0;k<G->numEdges;k++)
	{
		printf("输入边(vi,vj)上的顶点序号:\n");
		scanf("%d,%d",&i,&j);
		e = (EdgeNode *)malloc(sizeof(EdgeNode)); //向内存申请空间,生成边表结点
		e->adjvex = j; //邻接序号为j
		e->next = G->adjList[i].firstedge;
		G->adjList[i].firstedge = e; //将当前顶点的指针指向e
 
		e = (EdgeNode *)malloc(sizeof(EdgeNode));
		e->adjvex = i; //邻接序号为i
		e->next = G->adjList[j].firstedge;
		G->adjList[j].firstedge = e; 
	}
}
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值