近段时间在学习线段树,二叉树,字典树,网络流,图,都需要动态内存分配和建立链式结构,每次都是要看好长时间,一个朋友告诉我,好好看看单链表,把最基础的抓住,其他的就会迎刃而解。
动态内存分配对建立表、树、图和其他链式结构是特别有用的。
单链表
结构体声明类型:
struct node
{
int date;
node *next;
};
单链表的建立
struct node *p, *head;
head=NULL;
p=(struct node *)malloc(sizeof(struct node));//为节点分配内存空间
scanf("%d", &p->date);//把数据存储到节点中或者直接赋值 p->date=a;
p->next=head; head=p;//把节点插入到链表中,此方法为从头插入,如果你输入的是5 4 3 2 1,那么里面的顺序会是 1 2 3 4 5
单链表的遍历
struct node *p;//定义一个指针在链表之间移动;
p=head;//首先指向表中的第一个元素
while(p)//如果没有表尾就执行循环
{
printf("%d", p->date);//输出p所指节点的元素值
p=p->next;//让p指向下一个节点
}
单链表的插入.
一般从中间插入.此时就应该找到要插入的是哪一个结点后面,则称此节点为要插入节点的前驱节点,用pre表示
struct node *p, *pre;
p=(struct node *)malloc(sizeof(struct node));//创建一个新结点
p->next=pre->next;//将新节点的next指向前驱节点next指向的那个节点
pre->next=p;//将前驱节点的next指针指向新节点
单链表的删除
删除p点,此时我们就要找到p点的前驱节点
if(pre==NULL)
head=head->next;//如果要删除的是第一个节点,则改变头指针的方向
else
pre->next=p->next;//否则改变前一个节点,使它绕过要删除的节点
free(p);//释放要删除的p这个节点的空间
邻接表
写过之后我仍然无法明白,邻接表的原理,再接再厉;
朋友不建议我直接用vector 类,他说我这样用现成的会阻挡我的步伐,虽说现在进度是快了点,但如果最基础的如何建立都不明白,又何谈灵活的应用呢,根本转化不成自己的东西,他说让我先从最基础的学起,尽量不用那些C++里面的函数库,他说让我自己实现,这样我的代码和思维能力就会有很大的提高,加油!
说实话,我觉得邻接表很神奇,如果是我我可能就不会如此建立,这是参考别人代码,才知道这样建立的
邻接表,其实也要结构体类型声明
struct node
{
int date;
node *next;
}G[N];
int head[N];
memset(head, -1, sizeof(head));
int cnt=0;
void addedge(int a, int b)
{
G[cnt].date=b;
G[cnt].next=head[a];
head[a]=cnt;
cnt++;
}
说实话,我是看了两天才明白了最基础的,智商不够啊,很可惜,没有人真正的指点我,不过我搜了百度文库,看不明白;
给你一组数据代表a与b相连
1 2
1 4
2 4
2 3
3 4
cnt | 0 | 1 | 2 | 3 | 4 |
a | 1 | 1 | 2 | 2 | 3 |
G[cnt].date=b | 2 | 4 | 4 | 3 | 4 |
next=head[a] | -1 | 0 | -1 | 2 | -1 |
head[a]现在的值 也就是cnt++之后的值 |
head[1]=0 | head[1]=1 | head[2]=2 | head[2]=3 | head[3]=4 |
我们建立的邻接表其实是这样的(单向的)
1 与2 4相连
2 与4 3相连
3 与4 相连
head[0]=-1;
head[1]=1;
head[2]=3;
head[3]=4;
head[4]=-1;
你们又没有发现什么,类似与栈,也就是说cnt 0->1 如果遍历的话,就for(j=head[a]; j!=-1; j=G[j].next) 一般a从start开始一般是1,到end;这时候观察一下,head[1]=1;此时G[j] 刚好就是G[1]也就是与1相连的4,之后j=0;刚好是与1相连的2;类似与栈,我们将1压入栈底,之后压入4;所以我们寻找的时候首先搜索到的head[a]最后进栈的,之后再向前推,由此,你明白为什么G[cnt].next=head[a];
head[a]=cnt;也就是说这里的G[cnt].next存的是上一个与u相连的值,而head[a]=cnt就是记录此时的下标,方便为下一个的next赋值;
观察紫色与紫色数字之间的关系,红色与 红色数字之间的关系,再 看他们这两行 的‘a’值,竟然是相同的
而且head[a]=cnt;就本列来说; 所以你就会明白 其实next=head[a]的含义就是建立关系,即使是在G数组中
我建立的是单向的,如果是双向路这样做也可以,只用添加反边就行了;
通俗的说 与节点 a相连的第一条边是G[cnt].date,那么‘a’相连的下一条边是G[cnt].next;而G[cnt].next又等于下一条边的数组的下标
G[cnt1].next=head[a]; head[a]=cnt2; 你们看看领悟领悟 这里cnt1 与cnt2 就是与 ‘a’相连的边的数组的下标;
这里推荐一个博客,写的简洁明了,我搜的时候搜到的;http://blog.csdn.net/u014303647/article/details/38865357
大家可以观察一下,next这一行, 与cnt这一行的关系。其实就可以发现next其实代表与 ‘1’ 相连的第一个数是 ‘2’ 第二个数也就是(next)下一个数是 ‘4’;这就是为什么a不存入其中的原因;
同理 与‘2’ 相连的是‘4’ 之后的是‘3’;依次进入表中,看过这个大家也就明白为什么next=head[a]了吧,每次head[a]都会更新为cnt现在的值也就是记录,与‘a'相连的这个数‘b’所存在的位置;
当我们遍历的时候,就可通过寻找G[i].next来寻找与‘a’相连的其他数;因此每次addage这个函数里面 可以总结两条 更新过后的head[a]=cnt;
之前用的是#include<vector>这个头文件,但是其实这样不好,本末倒置了,如果我先理解上面那一种,我理解vector类就会更清晰,但是先理解vector类在理解基础的建立过程就会很艰难;
(1)vector<int>a;//相当于申请一个一维数组
a.push_back(i);//压入
a.size();//得到长度
a.clear();//清空
(2)vector<int>G[N];相当于vector<vector<int >>G//相当于申请一个二维数组
G.resize(n+1);//申请内存
G[i].push_back(j);
G[i].size();
G[i].clear();