链表结构--单链表

1.单链表结构

     我们想表示一个数据与直接后继数据的逻辑关系,也就是存储数据和存储该数据的下一个数据的地址。我们把这样的存储结构称作结点

datanext

data:存储数据,叫数据域   

next:存储后一数据地址,叫指针域

head:指向第一个结点,叫头指针

第一个结点之前的一个结点叫做头结点,它可以不存储任何东西,也可以存储单链表不包括的元素值。

图片来源于博客:先鱼鲨生

 

存储结构定义

Q1:为什么不能用Node *next?

  编译器无法识别,因为计算机还没有执行完typedef这块代码,所以必须用struct Node完整来表示。

Q2:为什么不能用顺序表ElemType *next?

  那是因为ElemType使用对象是数据域,Node才是指针域使用的,如果使用前者,它指向的数据本身,而非节点使用的。

typedef struct Node
{
	ElemType data; //数据域
	struct Node *next; //在Node结构体中定义一个指针成员next
}Node,*LinkList; //Node是给struct Node取的别名,*LinkList是给struct Node *结构体的指针取的别名

2.单链表基本操作

2.1 建立单链表

LinkList *Create(LinkList *L) {
	Node *r, *s; //定义*r为尾结点指针, *s为新结点指针
	ElemType c; //是数据变量
	int i; //i是循环变量
	    
//建立头节点, 初始尾结点指向头结点
	*L = (LinkList*) malloc(sizeof(Node)); //给头结点分配内存空间
	r = *L;  //r初始指向头结点
	for (i=1; i<=3; i++) { //循环3次添加3个新节点 
		printf("请输入第%d个数:", i);
		scanf("%d", &c); //从控制台接收数值给c
		s = (LinkList*)malloc(sizeof(Node)); //给新结点分配内存空间
		s ->data = c; //新结点的数据域赋值c
		s->next = NULL; //新结点的指针域为空
		//下面是给链表尾结点挂接新结点
		r->next = s; //将新结点插入当前表尾
		r = s; //r指向当前表尾
	} 
	return L; //返回头节点 
} 

不专业的帮助理解:首先头指针和尾指针指向头结点,*L为头指针,r为尾指针。

                  其次,生成一个新节点,s为新结点指针,为s弄一个存储空间,c为数据,把数据存储到数据域data,此时指针域next仍未空,因为咱们还没把这个新结点s插到表尾,以至于还没生成这个的下一个新结点,这个s-next无法储存地址。

               最后把这个结点弄到表尾,也就是用尾指针r-next地址存储这个新节点s,然后这个s结点成为尾结点,尾指针r就要从原来指向*L结点到现在尾s结点。

温馨提示:插入元素数据的位置i的范围:1≤i≤n+1

2.2插入数据

采用的原则:“先连后断”--重感情的,哈哈哈。

Status ListInsert(LinkList *L, int i, ElemType e) {
	Node *p, *s; //*p为尾结点, *s为新节点
	int j=0; //记录当前的位置
	// 步骤1: 查找插入位置的前驱节点 
	p = *L; //尾结点p指向头节点, 下面通过while去找到 要插入位置的尾结点 
	while( p!=NULL && j<i-1) {
		p = p->next; //p节点往下一节点移动 
		j++;// j要记录移动位置 
	} 
	if (j!=i-1) return FALSE; //如果没有找到 前驱节点位置,就直接返回失败状态
	// 步骤 2:生成新节点 
	s = (LinkList*)malloc(sizeof(Node)) ; //分配内存空间
	s ->data = e; //把数据元素赋值给新节点的数据域上
	// 步骤3: 重新关联指针域 
	s ->next = p->next; //将前驱节点的下一个节点连到 新节点的下一个节点上 
	p->next =s; //把新节点指向前驱节点的后继指针域
	return TRUE;//返回完成的状态 
}

Q3:插入带来哪些结构性改变?(先不管那些节点个数变化)

     首先我们改变是持续的,所以要用LinkList *L;其次,我们会引起元素变化e;最后选择那个位置进行插入。

Q4:为什么是i-1?

   由于头结点位置是0,但遍历又不得不从它开始,所以,i-1是我们需要找到插入的前结点位置。类比于数组的索引。

不专业的帮助理解:我们确定要插入那个位置,引入i,其次我们要记录找了几次,引入j,找到后带来尾结点和新节点指向问题变化,引入*p和*s。首先,p初值指向头结点。然后循环寻找,循环就一定有条件,首先要告诉它循环到表尾停止,其次循坏到我想出入前一个位置就行。

Q5:既然我知道它要弄到我要的前一个位置不就好,为啥还要有前一个条件?

  那是因为操作者并不知道生成了多少个结点,如果只有3个结点(含头结点),她告诉系统我要插第4个,但根本不存在。所以最终我们以2不等于3告诉它插入失败。但实际搜索到表尾都没找到,说明插入位置大于实际的结点个数,根本不用多此一举进行循环。

承接上面话继续,我们需要尾结点p不断移动移动去找到下一个,直到找到i-1为止。如果j=i-1说明找对了,便开始给这个新的结点开辟存储空间,把元素存储到数据域,由于AB2个结点是个讲感情的人,A想尽管新朋友C加入也不能忘老朋友B,所以加入结点C就是桥梁,A先看到新节点C和原来后结点B成为好朋友才愿意接纳C,所以s->next=p->next;然后p->next才愿意接受s。因此告诉大家这两行代码不能换位置(先连后断)。

2.3删除数据

  首先,我们进行链表删除,就要想到释放空间,运用到free()函数,这个要条件反射哦!

原则:先指向,在搭桥,后拆桥

Status ListDelete(LinkList *L, int i, ElemType *e) {
	Node *p, *r; //*p是i-1的节点, *r是被删除的节点
	int j=0;
	p=*L;//p初值指向头结点
	while (p && j<i-1) {
		p = p->next; //p节点往下一个节点移动
		j++; //记录移动的位置 
	} 
	if (j != i-1) return FALSE; //没有找打i-1的位置 
	r = p->next; //r指向要删除的节点
	p->next = p->next->next; //p的指针域指向删除节点的下一个节点上 
	e= r->data; //要删除的数据元素赋值给e
	free(r); //把删除的节点r释放内存空间
	return TRUE; 
} 

Q6:为什么不能是ElemType e?

      我们删除也需要修改外部,ElemType e会创建副本,其实质并未改变。

Q7:那与顺序表插入ElemType e又有怎样区别?

  顺序表是通过数据副本(值传递),它只用读取数据,不用返回,好比打印机,它通过复印就可以。但这里需要外部知道删除内容,不仅仅只让它消失,就好比一个亲人去世,尽管肉体消失(实质),可我们怀念他可以通过照片(外部留存)。

不专业帮助理解:同添加一样,要找到前一个结点。从头结点开始,循环条件也不变。r先指p的后一节点(相当于保存),然后搭桥p的后继点的连接删掉的后一结点的后继点。然后把删除的元素给e(交给它寄存),释放r。

2.4查询数据

     不做讲解,sorry!

完整代码见资源!!!

 

 

                       

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值