C语言程序设计 学习笔记 链表

接可变数组
但如果我们可以使用BLOCK,将其都拼接在一起,并不是用上面的方法复制粘贴。每一个BLOCK会有一个单元指向的是下一个BLOCK的地址,这样就不会有上述的问题了
在这里插入图片描述

所以对于一个单元,它里面应该分成两部分:
1.数据
2.下一个单元的地址(指针)
这样指向的下一个数据结构也应是如此。
直到最后一个单元,保存的是数据,地址用一个标记标志它已经没有指向下一个地方了
当然最开始,还需要一个指针指向第一个单元的地址,俗称head
这样的东西就叫做链表(linklist),每一个单元叫做节点
定义:

typedef struct _node{
	int value;
	struct _node *next;
} Node;

对于往链表内加入数据,代码实现如下:

#include <iostream>
#include "node.h"
#include <stdlib.h>
using namespace std;
//typedef struct _node{
//	int value;
//	struct _node *next;
//} Node;

int main(int argc, char** argv) {
	Node *head = NULL;//最开始head指向的是null 
	int number;
	do{
		cin>>number;
		if(number != -1){
			//add to linked-list,这是新的一个p,它是要放到最后去的 
			Node *p = (Node*)malloc(sizeof(Node));
			p->value = number;
			p->next = NULL;
			
			//find the last,找到目前链表中的最后一个节点
			//该节点的next还是null,我们要让其指向p 
			Node *last = head;
			if( last ){//最初始的时候last=head,head=null,所以此时的last->next是无效的,为了防止这个bug发生,添加if保险 
				while(last->next){//直到循环结束,那么last->next就是最后一个null了 
					last = last->next;
				}
				//找到目前链表的末尾,让其指向p 
				last->next = p;
			} else {
				head = p;//如果last是null,那么head就是p,这个else只会在初次赋值的时候用到 
			}
		}
	} while(number != -1);
	return 0;
}

2.链表的函数
在上述do-while函数内的代码,实质上就是一个对链表进行赋值的功能,那么我们可以将其封装成一个函数,这样更易使用,如:

#include <iostream>
#include "node.h"
#include <stdlib.h>
using namespace std;
//typedef struct _node{
//	int value;
//	struct _node *next;
//} Node;

void add(Node* head, int number);
int main(int argc, char** argv) {
	Node *head = NULL;//最开始head指向的是null 
	int number;
	do{
		cin>>number;
		if(number != -1){
			add(head,number)
		}
	} while(number != -1);
	return 0;
}

void add(Node* head,int number){
	Node *p = (Node*)malloc(sizeof(Node));
	//add to linked-list,这是新的一个p,它是要放到最后去的 		
	p->value = number;
	p->next = NULL;
	
	//find the last,找到目前链表中的最后一个节点
	//该节点的next还是null,我们要让其指向p 
	Node *last = head;
	if( last ){//最初始的时候last=head,head=null,所以此时的last->next是无效的,为了防止这个bug发生,添加if保险 
		while(last->next){//直到循环结束,那么last->next就是最后一个null了 
			last = last->next;
		}
		//找到目前链表的末尾,让其指向p 
		last->next = p;
	} else {
		head = p;//如果last是null,那么head就是p,这个else只会在初次赋值的时候用到 
	}
} 

但这样有非常严重的问题,在add函数中,我的head是一个传参,在函数中的head = p; 这个操作实质上并没有任何用,add函数用完后的传参被释放,head永远是NULL

解决方法
1.将Node *head设为全局变量
缺陷:全局变量是有害的,是一次性的,这个head只对一个链表起作用,如果一个程序有多个链表,全设为全局变量很危险

2.将add函数设置一个返回值head,执行完毕后将head的值返回回去

head = add(head, number);

这方法看似不错,但是在使用的时候感觉会很奇怪(尤其是给他人使用这个方法的时候)——只不过想add值进去,为什么要返回值呢?要返回也是返回add成功与否吧?

3.传head的指针回去
那这样,函数的应该为

void add(Node **pHead, int number);

但是用到**的时候就要注意,指针的指针是一个非常复杂的东西,一不小心会产生难以想象的错误,且指向指针的指针代码也不易于阅读

4.创建一个结构体list,里面存的是Node *head,再对其进行使用
函数调用的时候是调用list的地址,实质上其实是和3是一样的,但是换了一个更易阅读的方式。也就是通过自己定义的数据结构来代表整个list,代码:

#include <iostream>
#include "node.h"
#include <stdlib.h>
using namespace std;
//typedef struct _node{
//	int value;
//	struct _node *next;
//} Node;

typedef struct _list{
	Node *head;
} List;

void add(List *plist, int number);
int main(int argc, char** argv) {
//	Node *head = NULL;//最开始head指向的是null 
	List list;
	list.head = NULL;
	int number;
	do{
		cin>>number;
		if(number != -1){
			add(&list, number);
		}
	} while(number != -1);
	return 0;
}

void add(List *plist, int number){
	Node *p = (Node*)malloc(sizeof(Node));
	//add to linked-list,这是新的一个p,它是要放到最后去的 		
	p->value = number;
	p->next = NULL;
	
	//find the last,找到目前链表中的最后一个节点
	//该节点的next还是null,我们要让其指向p 
	Node *last = plist->head;
	if( last ){//最初始的时候last=head,head=null,所以此时的last->next是无效的,为了防止这个bug发生,添加if保险 
		while(last->next){//直到循环结束,那么last->next就是最后一个null了 
			last = last->next;
		}
		//找到目前链表的末尾,让其指向p 
		last->next = p;
	} else {
		plist->head = p;//如果last是null,那么head就是p,这个else只会在初次赋值的时候用到 
	}
} 

思考:每次加入新东西的时候都要用last从head开始从头寻找最后一个单元再做后续的动作,“每次都要遍历”这个操作非常有重复性并且浪费时间,那么我们能否就定义一个东西,让它一直指向的就是我们的最后一个单元呢?(视频内没讲了,提示:在list里再加一个Node *tail)

链表的搜索
遍历:
一个指针,拿到head,输出value
变到下一个地址
当没有下一个地址(最后指向的是null)循环结束
可以用for,也可以用while,for循环更加易懂:

	Node *p;//开始遍历
	for( p = list.head; p; p = p->next){
		cout<<p->value<<' ';
	} 
	cout<<endl;

同样的,可以封装成函数,取名为print,我们需要的只是head

void print(List *plist){
	Node *p;//开始遍历
	for( p = plist->head; p; p = p->next){
		cout<<p->value<<' ';
	} 
	cout<<endl;
}

既然能遍历了,那么我想寻找链表中某一个特定的数那也很方便了

cin>>number;
	Node *p;
	int isfound = 0;
	for(p = list.head; p; p->next){
		if(p->value == number){
			cout<<"find it"<<endl;
			isfound = 1;
			break;
		}
	}
	if(!isfound){
		cout<<"don't find it"<<endl;
	}

那么找到这个之后,我们想删除这块
单元,该如何考虑
1.该单元的前面一个单元所指的位置就不应该是待删除的这块了,而是下一块
2.free掉这个待删除的单元

1.1 如何找到上一个单元?
再创建一个指针q,那个指针指向它前面那个单元,这样只要让q.next = p.next就可以安心free(p)

Node *q;
	for(q = NULL,p = list.head; p; q=p,p = p->next){//先让q=p,p再向后走,这样的q就是上一个p了 
		if(p->value == number){
			q->next = p->next;
			free(p);
			break;
		}
	}

但是这样有一个问题,q初始值是null,对于如果第一个单元就是要去掉的,此时我们要修改的是head所指的地方,而不是对q有什么操作。
在如此情况下,上述代码中的q->next是非法、无意义的
所以我们需要通过语句将其保护起来(让代码更加严谨)
代码实现:

Node *q;
	for(q = NULL,p = list.head; p; q=p,p = p->next){//先让q=p,p再向后走,这样的q就是上一个p了 
		if(p->value == number){
			if(q){
				q->next = p->next;
			} else {
				list.head = p->next;
			}
			free(p);
			break;
		}
	}

链表的清除
每次遍历到一个,先有一个临时变量存储next,然后free(),接着到下一个节点,继续free,直到p=NULL结束

	for( p = list.head; p; p=q){
		q = p->next;
		free(p);
	}

程序设计入门-链表到此为止,至此将此课的代码全部奉上:
node.h:

#ifndef _NODE_H_ 
#define _NODE_H_

typedef struct _node{
	int value;
	struct _node *next;
} Node;

#endif

main.cpp:

#include <iostream>
#include "node.h"
#include <stdlib.h>
using namespace std;
//typedef struct _node{
//	int value;
//	struct _node *next;
//} Node;

typedef struct _list{
	Node *head;
} List;

void add(List *plist, int number);
void print(List *plist);
int main(int argc, char** argv) {
//	Node *head = NULL;//最开始head指向的是null 
	List list;
	list.head = NULL;
	int number;
	do{
		cin>>number;
		if(number != -1){
			add(&list, number);
		}
	} while(number != -1);
//	print(&list);

	cin>>number;//寻找值 
	Node *p;
	int isfound = 0;
	for(p = list.head; p; p->next){
		if(p->value == number){
			cout<<"find it"<<endl;
			isfound = 1;
			break;
		}
	}
	if(!isfound){
		cout<<"don't find it"<<endl;
	}
	
	Node *q;//删除指定值 
	for(q = NULL,p = list.head; p; q=p,p = p->next){//先让q=p,p再向后走,这样的q就是上一个p了 
		if(p->value == number){
			if(q){
				q->next = p->next;
			} else {
				list.head = p->next;
			}
			free(p);
			break;
		}
	}
	
	for( p = list.head; p; p=q){//链表的清除 
		q = p->next;
		free(p);
	}
	return 0;
}

void add(List *plist, int number){
	Node *p = (Node*)malloc(sizeof(Node));
	//add to linked-list,这是新的一个p,它是要放到最后去的 		
	p->value = number;
	p->next = NULL;
	
	//find the last,找到目前链表中的最后一个节点
	//该节点的next还是null,我们要让其指向p 
	Node *last = plist->head;
	if( last ){//最初始的时候last=head,head=null,所以此时的last->next是无效的,为了防止这个bug发生,添加if保险 
		while(last->next){//直到循环结束,那么last->next就是最后一个null了 
			last = last->next;
		}
		//找到目前链表的末尾,让其指向p 
		last->next = p;
	} else {
		plist->head = p;//如果last是null,那么head就是p,这个else只会在初次赋值的时候用到 
	}
} 
void print(List *plist){
	Node *p;//开始遍历
	for( p = plist->head; p; p = p->next){
		cout<<p->value<<' ';
	} 
	cout<<endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值