链表的知识理解和实现

最近实现了单链表的创建,插入,删除,寻找元素,以及输出,删除链表的操作。这里记录下自己对链表的理解。

首先是链表,链表有两个属性,一个是数据属性(域),另一个是指针属性(域),链表的理解通俗的来讲可以想成老师,让学生手拉手连接起来,这样就形成了一条人形长链,每一个学生,就是一个 “结点”,老师是头结点(head)。要一个结点就找一个学生拉上去;不要一个结点就让那个学生手放开,空出的两个学生手拉在一起……
我也可以把链表想象成一条铁链,要一个结点就焊接一个上去;不要一个就敲碎一个,把敲碎那个的旁边两个重新焊接起来;要加一个数据上去就把要插入的位置的那个节点给融开,把新的结点焊上去。

  • 与链表有关的两个函数:

1:动态申请内存 malloc 函数 (memory allocation)
其原型是:

extern void *malloc(unsigned int num_bytes);
void * 
是未确定类型的指针,要用户指定类型,如char *, int *,double *
若没有指定则会报错.
如果申请成功,则返回被指向的内存的指针,否则返回 NULL

与之相对应的是free()函数,用来释放由malloc给指针变量申请内存空间。
其原型是:

void free(void *P)
  • 结点结构体的声明
struct node
{
	int date;              //数据域
	struct node *next;     //指针域
};
  • 链表头结点的建立
    代码实现:
void ListInitiate(node **head)           //二级指针
{
	if( (*head = (node *)malloc(sizeof(node))) == NULL)
		exit(1);
	(*head)->next = NULL;
}

这里传递的是主函数中指针变量head的地址,而不是值。

  • 链表的数据录入
void ListDate(node **head)                  //二级指针
{
	node *p1, *p2;
	p1 = p2 = (node *)malloc(sizeof(node));
	scanf("%d", &p1->date);
	(*head)->next = p1;	                    //重要: 头指针的指针域指向第一个结点
	while(p1->date != 0)                    //结束条件
	{
		p1 = (node *)malloc(sizeof(node));  //新的节点
		scanf("%d",&p1->date);              //数据录入
		p2->next = p1;                      //指向新的结点所在的位置
		p2 = p1;                            //存放新开辟的内存,便于下一次使用
	}
	p2->next = NULL;                        //最后一个结点指向NULL
}

在主函数中,我将头指针的地址传过去,因为如果将头指针传的值过去,是无法达到建立数据的效果,所以要传递指针的地址,也就是在 ListDate() 函数的参数列表内要声明一个二级指针。
此函数的作用,先给p1, p2动态分配一个大小为 sizeof(node) 的内存空间。
头指针的指针域指向第一个结点所在的内存位置。
录入数据的结束条件为输入的数据为 0.
录入第二个数据的时候,要将第一个结点的指针域,指向第二个结点所在的内存位置。
即,录入第 n 个数据,将第 n - 1 个结点的指针域,指向第 n 个结点所在的内存位置。
这就是链表的连接处理,通过指针域,将一个个结点连接起来,达到整体的目的。这也就存在一个问题,不像数组那样,知道下标便可以直接输出数据,而是读取的时候都要从头指针遍历起。
注意 2 点:

  1. 最后一个结点的指针域,一定指向NULL
  2. p -> next 中保存的,都是下一个结点所在的内存地址,这个内存地址是由 malloc 函数分配的,是一整块的,具有两个数据域的。(这点要着重理解)
  • 数据的插入.
    要求:在第 n 个结点前插入一个数据,成功返回1,失败返回0
    代码实现:
int ListInsert(node *head, int n, int x)
{
	int k = 0;
	node *p1, *p2;
	p1 = head;
	while(k < n - 1 && p1 != NULL) //移动到n-1的位置并且p1不可以为空
	{
		p1 = p1->next;			 
	}
	if(k != n - 1)                 //若此时没有指到第 n - 1个结点
		return 0;				  
	if((p2 = (node *)malloc(sizeof(node))) == NULL)  //新开辟一个内存
		exit(0);
	else
	{
		p2->date = x;              //放入数据内容
		p2->next = p1->next;       //新的指针域指向第 n 个数据的位置
		p1->next = p2;             //当前第 n-1 个指向新开辟的内存位置
		return 1;
	}
}

假设要插入的为结点 x
要实现在第 n 个结点前插入一个数据,就必须得找到第 n - 1 个结点的地址,这样才能将数据插在第 n 个后面。
当指针到达第 n - 1 这个位置后,就将 x 的指针域指向第 n 个结点所在的内存地址。
即该句:p2->next = p1->next; 由于 p1 在前面指向了第 n - 1个地址,故 p1->next 是第n个所在的内存地址。
最后令第 n - 1 个节点指向新开辟的内存地址即可实现成功加入数据。

  • 链表数据的删除
    要求: 删除第 n个结点,成功返回1,x存放被删除的值
    代码实现:
int ListDelete(node *head, int n, int *x)
{
	node *p1, *fn;                      //fn为要释放的结点位置
	int k = 0;
	p1 = head;
	while(k < n - 1 && p1->next != NULL && p1->next->next != NULL)
	{
		p1 = p1->next;                  //使指针指向第 n-1 个位置
		*x = p1->next->date;            //值的存放
		k++;
	}
	if(k != n - 1)
		return 0;
	fn = p1->next;                      //存放第 n 个指针地址
	p1->next = p1->next->next;          //使第n-1个指针域指向第n+1个结点地址
	free(fn);           			    //删除完毕后要释放该内存
	return 1;
}

删除第 n 个元素,就必须先移动到第 n - 1个位置,在将其前一个结点(n-1)和其后一个结点(n+1)连接起来,最后释放该结点内存即可。
注意:
p1->next = p1->next->next; 中, p1在前面的循环中,指到了第 n-1个结点,所以p1->next中存放的是下第 n个结点的地址,即p1->next->next的意思就是取出第 n个结点的指针域里面的值,而里面存放的是第 n+1个结点位置。

p1->next = p1->next->next; 也可以这样想:

P(n-1)->next = Node(n);    //第n个结点地址是保存在前一个结点指针域中的
Node(n)->next = Node(n+1); //第n个结点的指针域中存放是第n+1个结点的位置
P(n-1)->next->next = Node(n+1); //故有...

我觉得还可以这样思考:
赋值号的左边是固定的,表示就是第n-1个结点的指针域,右边是动态的,一个next就表示向这链表前面移动一个,几个移动几次,这里从 n-1 移动了两次,故变成了第 n+1个结点位置。
感觉第二种思考方式更简单的…
啊,再次感觉指针理解起来难。

  • 链表长度的计算
    代码实现:
int ListLength(node *head)
{
	int i = 0;
	while(head != NULL)  //当指针为空的时候,就表明到了链表尾巴了。
	{
		head = head->next;
		i++;
	}	
	return i;
}

  • 链表数据的输出
    代码实现:
void PrintList(node *head)
{
	head = head->next;     //头指针指向第一个结点
	while(head != NULL)    //为空的时候就跳出循环
	{
		printf("%d ", head->date);
		head = head->next; //移动一个
	}
}
  • 链表的删除
    代码实现:
void DestoryNode(node **head)  //传递头指针变量的地址进来
{
	node *p1, *f;          //p1控制链表移动,f存放将要释放的结点
	p1 = *head;
	//int i=1;
	while(p1 != NULL)
	{
		f = p1;        	   //指针变量f为释放中介,移动一个释放一个
		p1 = p1->next;     //结点移动
		free(f);
		//观察链表的属性,可以得到一个物理连接的直观数据
		//printf(fp,"\n第 %d 个结点,其地址为: %d, 其指针域为: %d",i++,p2,p2->next);
	}
	*head = NULL;           //头指针指向空,防止野指针。
	printf("释放成功\n");
}

这里注释掉的两段是我想看下链表的链接方式,可以更加直观的看到数据,也方便进一步理解链表是如何一个一个通过指针链接起来的。

到此,链表的一些基本操作已经完成(可能有错误的地方欢迎指出)。
记得当时在学习链表的时候,理解能力不够,便放弃了,经过了高中的洗礼,理解能力跟上了,再次看链表的时候,一下子就理解了这个抽象的东东,开心啊。这篇博文就简单的记录下我对于链表的理解吧哈哈哈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

C01acat

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

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

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

打赏作者

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

抵扣说明:

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

余额充值