链表的复习

一.动态变量与静态变量

静态变量:编译时直接分配存贮空间

动态变量:没有显示声明,没有名字,不给分配空间,一般通过指针标识。
1.程序的内部存储结构:

首先是目标代码区,然后是静态存储区,接着是库程序代码区,剩余的空间就是栈区和堆区,从剩余空间两端各自向中间延申。

2.管理动态变量

(1)申请内存空间

T*ptr;

ptr=(T*)malloc(sizeof(T));

注:T说明申请空间要保存的数据对象的类型,*标明是一个指针,malloc()函数申请sizeof(T)大小的内存,并返回这个内存区域的首地址。在这里使用了一个强制类型转换保证将其转化成T类型的指针。

(2)释放内存空间

free(ptr);

注意:以下情况不要使用free();

1)ptr无值

2)ptr值为NULL

3)ptr的空间不是malloc()申请来的

4)对一次申请的存储区多次释放

二.链表

 先来看一个实例,考虑这样的一个程序,输入一年内看过的所有电影,并储存其信息(片名、导演、主演、片长、种类、评级),使用一个链表来组织数据:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define TSIZE 45
/*->是C语言和C++语言的一个运算符,叫做指向结构体成员运算符,用处是使用一个指向结构体或对象的指针访问其内成员。*/
struct film{
	char title[TSIZE];
	int rating;
	struct film*next;
};
char * s_gets(char * st, int n)
{
    char * ret_val;
    char * find;
    
    ret_val = fgets(st, n, stdin);
    if (ret_val)
    {
        find = strchr(st, '\n');   // look for newline
        if (find)                  // if the address is not NULL,
            *find = '\0';          // place a null character there
        else
            while (getchar() != '\n')
                continue;          // dispose of rest of line
    }
    return ret_val;
}
int main(void){
	struct film*head=NULL;
	struct film*prev,*current;
	char input[TSIZE];
	/*收集并存储信息*/
	puts("Enter first movie title:");
	while(s_gets(input,TSIZE)!=NULL&input[0]!='\0')
	{
		current=(struct film*)malloc(sizeof(struct film));
		if (head==NULL)
			head=current;
		else
			prev->next=current;
		current->next=NULL;
		strcpy(current->title,input);
		puts("Enter your rating<0-10>:");
		scanf("%d",&current->rating);
		while (getchar()!='\n')
			continue;
		puts("Enter next movie title(empty line to stop):");
		prev=current;
	 } 
	 /*显示电影列表*/
	 if(head==NULL)
	 	printf("No data entered.") ;
	else
		printf("Here is the movie list:\n");
	while (current!=NULL)
	{
		printf("Movie:%s Rating:%d\n",current->title,current->rating);
		current=current->next;
	}
	/*完成任务,释放已经分配的内存*/
	current=head;
	while(current!=NULL)
	{
		current=head;
		head=current->next;
		free(current);
	 } 
	 printf("Bye!\n");
	 return 0;
}

这个程序实现了两个功能,构造链表并存储数据,显示链表中的内容。

1.显示链表

先来看链表的结构

struct film{
	char title[TSIZE];
	int rating;
	struct film*next;
};

在这里有三个成员,其中next成员就是保存下一个链表的地址。

并且我们已经设置了链表头指针的指向:

	struct film*head=NULL;

从而如果要设置当前指向第一个结构的指针,我们只需要让film类型的指针current和head有相同的指向。

current=head;

从而我们可以访问结构内的成员,然后再根据存储在next成员中的信息来让current指向下一个结构。

实现:

current=current->next;

重复上述过程,直到指向最后一个元素,此时next的值已经被设定成NULL.

2.创建链表

和动态变量一样,创建一个链表,我们只需要分配空间,储存地址,写入信息即可。

首先,链表的首个地址应该放在head里,后面的链表元素依次放在next成员里就可以了。

后面调用next很简单,但是要确定我们处理的是不是第一个结构,因而我们考虑用head的值来进行判断。

从而我们有这样的代码:

if (head==NULL)
    head=current;//程序开始时初始化指针为NULL,从而判断是否为NULL确定是不是第一个元素
else
    prev->next=current;

在这里指针prev指向了上次分配的结构。

接下来,为链表的成员设置合适的值,尤其是把next成员设置成NULL,表明当前结构是链表的最后一个结构,然后给title,rating赋值。

		current->next=NULL;
		strcpy(current->title,input);
		puts("Enter your rating<0-10>:");
		scanf("%d",&current->rating);

最后为下一次输入做准备,让prev指向当前结构,因为下次输入时当前结构会成为正在输入结构的前一个结构,从而设置:

prev=current;

最后调用free()释放内存即可。

三.链表操作

下面给出一些链表操作:

创建链表

遍历链表

在链表中检索

插入

删除

交换位置

首先,我们假设有这样的ADT类型,声明一个简单的单项链表:

typedef ... items;
typedef struct cell{
    items data;
    struct cell*next;
}celltype;
typedef celltype*pcelltype;
pcelltype top,rear;

 

 (1)下面考虑创建一个单向的链表,也就是用一项一项的数据逐步建立行程一个链表,可以分成向链表头加入数据和向尾部加入数据两种方式,因而定义:

void push(items x){
    pcelltype p;
    p=(pcelltype)malloc(sizeof(celltype));
    p->data=x;
    p->next=top;
    top=p;
}//向链表头部添加数据

p是一个指针,首先给data元素赋x,给p的next元素赋链表的链头指针top,也就代表p的下一个元素是原来的头部,而从头部加入数据代表p是新的头,从而让top=p,实现了从头部加入数据的操作。

/*假设rear为表尾部指针,将新的元素从尾部加入链表,方法如下*/
void InRear(items x){
    pcelltype p;
    p=(pcelltype)malloc(sizeof(celltype));
    p->data=x;
    p->next=NULL;
    if (rear==NULL){//空链
        rear=top=p;
    }else{
        rear->next=p;//非空链表
        rear=p;
    }
}

 (2)单向链表的遍历

遍历:从头到尾将链表中的所有数据项都加工一次。

p=top;
while(p!=NULL)
{    //加工p->data
    p=p->next;
}

可以再加入一个指针保留其前驱的位置。

p0=NULL;//指向前驱
p=top;
while(p!=NULL){
//加工p-data
    p0=p;
    p=p->next
}

(3)插入

假设我们现在有p0和p是相邻的链表元素,P0是p的前驱,考虑在中间插入r,则只需要定义好r的数据项,将p0中的next成员修改成r的地址,让r中的next指向p,就可以成功插入r。

r->next=p;
p0->next=r;//插入已经完成
p0=r;//让p0仍然是p的前驱

(4)删除

删除p所指向的链表

p=p->next;
free(p0->next);
p0->next=p;

实际上很容易理解,删除p所指向的,也就代表删除后p指向删除项后面的,因此第一句就是这样,然后释放p原指向的项的内存,因为p的指向已经被修改,所以在这里用p0的next元素来查找它的地址,释放后再让next保存p现在的位置就可以了。

(5)交换

通过地址的对换来实现链表元素的交换。

一方面可以把基本数据部分交换,但是在数据量大的时候比较浪费。

将p所指向的项与q交换:

/*交换p->next,q->next*/
g=p->next;
p->next=q->next;
q->next=g
/*交换p0->next,q0->next*/
p0->next=q;
q0->next=p;
/*交换p,q*/
p=p0->next;
q=q0->next;

 我们首先来看图a,指出了要交换数据项的链表(p指向的链表和q指向的链表分别是2,5)

2的next本来应该指向3,5的next应该指向6,但是在前5句完成之后,2的next指向了6,5的next指向了3,1指向5,4指向2.

在6-7行执行后,我们注意到,此时p指向p0的next,p0指向1,1指向5,即p指向5

同理,q指向q0的next,也就是4的next(2),实现了2和5的对换,即p指向5,q指向2,而其关系不变,也就是p0是p的前驱,q0是q的前驱。

再来回顾以下交换,实际上在这里我们一共需要四个指针,各自指向如下:

p0-1,p-2;q0-4,q-5

最基本的想法就是改成

p0-1,p-5;q0-4,q-2,与此同时各自相邻的指针仍然是相邻的。

首先就可以确定,由于p0/p相邻,则p0指向的1的next成员修改后应该保存的是5的地址,而q是此前指向5的指针;同理,q0指向的4的next成员应该在修改后保存的是2的地址,这是可以明确的,就是4,5两行代码。

由于交换的是基本数据,我们不需要交换pq各自的next成员,也就是说5的next应该是3,2的next是6的地址。这一步放在4,5前面进行。

然后保证各自相邻的顺序用p0/q0赋值即可。

(6)单向链表中检索

p0=NULL;/*前驱位置*/
p=top;/*当前位置*/
while(p!=NULL&&p->key!=key0){//查找等于给定关键字的数据节点,找到就返回相应的指针,否则带回NULL
    p0=p;//非空且不等
    p=p->next;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值