【数据结构与算法-C语言版】链表(单链表)的基本操作及C语言实现

程序来自:

http://data.biancheng.net/view/5.html

注释为自己手动添加,仅供交流学习,欢迎指导。

#include 
#include 
#define PAUSE printf("Press Enter key to continue..."); fgetc(stdin);


// 如果在结构体中用本结构体的变量,那么相当于一个递归,
// 编译器永远不会知道一个结构体的大小,或者说结构体大小无穷大。
// 而用本结构体指针,不管是什么数据,反正你申明一个指针就是4字节,
// 这个编译器还是没问题的。
typedef struct Link{
	int  elem;//代表数据域

	//这个用法很好,不能用 link *next 来定义
	//这肯定是一个惯例用法,不需要考虑太多。
	//链表中存放的不是基本数据类型,需要用结构体实现自定义
	struct Link *next;//代表指针域,指向直接后继元素
	
}link;

//定义了6个子函数,其中4个是 link 型子函数,一个是 int 型子函数,一个是void型子函数
link * initLink();

//链表插入的函数,p是链表,elem是插入的结点的数据域,add是插入的位置
link * insertElem(link * p, int elem, int add);

//删除结点的函数,p代表操作链表,add代表删除节点的位置
link * delElem(link * p, int add);

//查找结点的函数,elem为目标结点的数据域的值
int selectElem(link * p, int elem);

//更新结点的函数,newElem为新的数据域的值
link *amendElem(link * p, int add, int newElem);

void display(link *p);



//这个不叫成员函数,它就是 link型子函数,类似于 int FUNC() 
link * initLink(){
	link * p = (link*)malloc(sizeof(link));//创建一个头结点,为之分配内存,并指定头指针
	//p 不是头结点,而是头指针!
	//p 是头指针,指向头结点
	//sizeof(link) = 8,link不是一个指针,而是一个结构体变量
	link * temp = p;//声明一个指针指向头结点,用于遍历链表,遍历的过程与头指针 p 无关
	//生成链表
	for (int i = 1; i<5; i++) {
		link *a = (link*)malloc(sizeof(link));		//循环5次,每次都创建一个结点
		a->elem = i;
		//为什么是空值呢——为了在开始初始化的时候,通过 for 循环初始化链表
		a->next = NULL;		//这样会让最后一次赋值的 a->next 指向空值,这个办法非常好	
		temp->next = a;		//上一次定义的temp是a的前驱结点
		temp = temp->next;		//定义temp是a,这样为下一次循环赋值做准备
	}
	return p;		//return 了p 与temp无关,由此也可以看出 temp 是个中间量
}



link * insertElem(link * p, int elem, int add){
	link * temp = p;//创建临时结点temp, temp 等于头指针p ,这样就可以从链表首结点遍历
	//首先找到要插入位置的上一个结点
	for (int i = 1; inext;		//temp 是要插入位置的上一个结点	
	}
	//创建插入结点c
	link * c = (link*)malloc(sizeof(link));		//插入一个节点,因为
	//因为 link * c 在子函数结束时要存到 p 中返回,所以在子函数结束时不要 delete 释放
	c->elem = elem;
	//向链表中插入结点
	c->next = temp->next;
	temp->next = c;
	return  p;
}


link * delElem(link * p, int add){
	link * temp = p;
	//遍历到被删除结点的上一个结点
	for (int i = 1; inext;
	}
	link * del = temp->next;//单独设置一个指针指向被删除结点,以防丢失
	temp->next = temp->next->next;//删除某个结点的方法就是更改前一个结点的指针域
	//牛呀!居然只要改动前一个节点的指针域就行了!



	//temp->next 其实在初始化时就已经 malloc 分配了内存,所以在删除后要释放 free
	// 在程序运行的整个过程中,申请的内存空间不会自己
	// 释放(只有当整个程序运行完了以后,这块内存才会被回收)
	free(del);//手动释放该结点,防止内存泄漏
	return p;
}




int selectElem(link * p, int elem){
	//使用中间变量指针 t的目的是为了保证头指针 p 一直指向头结点,而不要变化
	link * t = p;		
	int i = 1;		//从1开始查找,因为前面有一个头结点,此时 首元结点 不是头结点
	//当 t->next 不为空时
	while (t->next) {
		t = t->next;		//t->next 本来就是 link 型指针
		if (t->elem == elem) {
			return i;
		}
		i++;
	}
	return -1;		//如果查找失败,返回-1
}


//更新函数,其中,add 表示更改结点在链表中的位置,newElem 为新的数据域的值
//amendElem  不是addElem
link *amendElem(link * p, int add, int newElem){
	link * temp = p;
	temp = temp->next;//temp指向首元结点
	//这一点是聪明的,把程序简化了,这样程序就不用再写成 temp->next->elem = newElem
	//temp指向被删除结点
	for (int i = 1; inext;		//temp是从前向后一层一层地追,直到追到需要被 add 的地方
	}
	temp->elem = newElem;
	return p;
}



void display(link *p){
	link* temp = p;//将temp指针重新指向头结点		//包含了头结点
	//只要temp指针指向的结点的next不是Null,就执行输出语句。
	while (temp->next) {
		temp = temp->next;
		printf("%d", temp->elem);
	}
	printf("\n");
}






int main() {
	//初始化链表(1,2,3,4)
	printf("初始化链表为:\n");
	link *p=initLink();
    display(p);


	printf("在第4的位置插入元素5:\n");
	p = insertElem(p, 5, 4);
	display(p);

	printf("删除元素3:\n");
	p = delElem(p, 3);
	display(p);

	printf("查找元素2的位置为:\n");
	int address = selectElem(p, 2);
	if (address == -1) {		//这一步好聪明呀!!!
		printf("没有该元素");
	}
	else{
		printf("元素2的位置为:%d\n", address);
	}
	printf("更改第3的位置的数据为7:\n");
	p = amendElem(p, 3, 7);
	display(p);

	return 0;
}

 

你可能感兴趣的:(C语言与科学计算)