目录
一、问题的提出
二、链表的定义
三、单向链表的建立
四、单向链表的删除操作
五、单向链表的插入操作
数组实质是一种顺序存储,随机访问的线性表,它的优点是使用直观,便于快速、随机地存取线性表中地任意元素。但缺点是对其进行插入和删除操作时需要移动大量地数组元素,同时由于数组数据静态内存分配,定义数组时必须指定数组地长度,程序一旦运行,其长度就不能在改变,若想改变,只能修改程序,实际使用地数组元素个数不能超过数组元素最大长度地限制,否则就会发生下标越界地错误,而低于所设定地最大长度时,又会造成系统资源浪费,因而空间效率差。
所以有没有更合理使用系统资源地方法呢?即当需要添加一个元素时,程序可以自动地申请内存并添加,而当需要删除一个元素时,程序又可以自动地放弃该元素原来占用地内存。
方法就是使用动态数据结构。它是利用动态内存分配、使用结构体并配合指针来实现地一种数据结构。先看看下面地结构体代表地是什么意思:
struct temp
{
int data;
struct temp pt;
};
这个定义将出现错误警告。
这说明,结构体声明时不能包含本结构体类型成员。因本结构体类型尚未定义结束,它所占用地内存字节数尚未确定,因此系统无法为这样地结构体成员分配内存。
然而,在声明结构体类型时可以包含指向本结构体类型地指针成员。这是因为变量存放地数据是地址,系统为指针变量分配的内存字节数(即存放地址所需要的内存字节数)是固定的(对于32位计算机系统是4个字节),不依赖于它所指向的数据类型。包含指向本结构体类型的指针成员的结构体类型说明方式如下:
struct temp
{
int data;
struct temp *pt;
}
结构体和指针配合使用可表示许多复杂的动态数据结构,如链表(Linked Table)、堆栈(Stack)、队列(Queue)、树(Tree)、图(Graph)等。其中链表又包括单链表、双链表和循环链表等。
链表实际是链式存储、顺序访问的的线性表,与数组不同的是,它是用一组任意的存储单元来存储线性表中的数据,存储单元不一定是连续的,且链表的长度不是固定的。链表数据结构体的这一特点使其可以非常方便地实现节点的插入和删除操作。
链表的每个元素称为一个节点(Node),每个节点都可存储在内存中的不同位置。为了表示每个元素与后继元素的逻辑关系,以便构成“一个节点链着一个节点”的链式存储结构,除了存储元素本身的信息之外,还要存储器直接后继信息。因此,每个节点都包含两个部分:第1部分称为链表的数据域,用于存储元素本身的数据信息,即用户需要的数据,这里用data表示,它不局限于一个成员数据,也可是多个成员数据;第2部分是一个结构体指针,称为链表的指针域,用于存储其直接后继的节点信息,这里用next表示,next的值实际上就是下一个节点的地址,即next指向下一个节点,当前节点为末节点时,next的值设为空指针(NULL),表示链表的结束。为简单起见,通常在示意图中用符号“^”来表示。
为表示链表结构,必须在结构体中定义一个指针类型的成员变量,用它来存储下一个节点的地址,并且该指针变量必须具有与结构体相同的数据类型。例如:
struct link
{
int data;
struct link * next;
}
此外,链表还必须有一个指向链表的起始节点的头指针变量head。
我们采取向链表中添加节点的方式来建立一个单向链表。为了向链表中添加一个新的节点。首先要为新建节点申请动态内存,让指针p指向这个新建的节点,然后将新建节点添加到链表中,此时需要考虑两种情况:
(1)若原链表为空链表,则将新建节点置为头节点;
(2)若原链表为非空,则将新建节点添加到表位。
根据上述思想编写向链表添加节点数据的程序:
#include
#include
struct link *AppendNode(struct link * head);
void displynode(struct link *head);
void deletememory(struct link * head);
struct link
{
int data;
struct link *next;
};
int main(void)
{
int i=0;
char c;
struct link * head = NULL; //链表头指针
printf("Do you want to append a new node (y/Y)?");
scanf(" %c",&c); //%c前有一个空格
while(c=='y'||c=='Y')
{
head = AppendNode(head);//向head为头指针的链表末尾添加节点
displynode(head); //显示当前链表中个节点的信息
printf("Do you want to append a new node (Y/y)?");
scanf(" %c",&c); //%c前有一个空格
i++;
}
printf("%d new node have been apended! \n",i);
deletememory(head); //释放所有动态分配的内存
}
//函数功能:新建一个节点并添加到链表末尾,返回添加节点后的链表的头指针
struct link * AppendNode(struct link*head)
{
struct link *p=NULL,*pr = head;
int data;
p = (struct link*)malloc(sizeof(struct link));//让p指向新建节点
if(p==NULL) //若为新建节点申请内存失败,则退出程序
{
printf("No enough memory to allocate!\n");
exit(0);
}
if(head==NULL) //若原链表为空表
{
head=p; //将新建节点置为头节点
}
else //若原链表为非空,则将新建节点添加到尾表
{
while(pr->next!=NULL) //若未到尾表,则移动pr直到pr指向尾表
{
pr=pr->next; //让pr指向下一个节点
}
pr->next=p; //让末节点的指针域指向新建节点
}
printf("Input node data:");
scanf("%d",&data); //输入节点数据
p->data= data; //将新建节点的数据域赋值为输入的节点数据
p->next=NULL; //将新建节点置为表尾
return head; //返回添加节点后的链表的头指针
}
//函数的功能:显示链表中所有节点的节点符号和该节点中的数据项内容
void displynode(struct link * head)
{
struct link *p = head;
int j=1;
while(p!=NULL) //若不是表尾,则循环打印节点的值
{
printf("%5d%10d\n",j,p->data); //打印第j个节点的数据
p= p->next; //让p指向下一个节点
j++;
}
}
//函数功能:释放head 指向的链表中的所有节点占用的内存。
void deletememory(struct link * head)
{
struct link *p = head,*pr = NULL;
while(p != NULL) //若不是尾表,则释放节点占用的内存
{
pr= p; //在pr中保存当前节点的指针
p= p->next; //让p指向下一个节点
free(pr); //释放pr指向的当前节点占用的内存
}
}
链表的删除操作就是将一个待删除节点从链表中断开,不在于链表中的其他节点有任何联系。为了在已有的链表中删除一个节点,需要考虑如下四种情况:
(1)若原链表为空,则无需删除节点,直接退出程序;
(2)若找到待删除节点p是头节点,则将head指向当前节点的下一个节点(head=p->next),即可删除当前节点;
(3)若找到的待删除节点不是头节点,则将前一节的指针域指向当前节点的下一节点(pr->next=p->next)即可伤处当前节点。当待删除节点是末节点是,执行pr->next=p->next。
(4)若以搜索到表尾(p->next==NULL),仍未找到待删除节点,则显示“未找到”。
注意:节点被删除后,指标是将它从链表中断开而已,它仍然占用内存,必须释放其所占的内存,否则将出现内存泄漏。
struct link * deletenode(struct link*head,int nodedata)
{
struct link * p=head,*pr=head;
if(head=NULL) //若链表为空,则退出程序
{
printf("Linked table is empty!\n");
return(head);
}
while(nodedata!=p->data && p->next!=NULL)//未找到且未到末尾
{
pr=p;
p=p->next;
}
if(p->data==nodedata)
{
if(p==head)
{
head=p->next;
}
else
{
pr->next= p->next;
}
free(p);
if(head==NULL)
printf("over!");
}
else
{
printf("This Node has not been found!\n");
}
if(head!=NULL)
return head;
else exit(0);
}
向链表中插入一个新的节点时,首先要新建一个节点,将其指针域赋值为空指针(p->next=NULL),然后再链表中寻找合适的位置执行节点插入操作,此时需要考虑以下四种情况。
(1)若原链表为空,则将新节点p作为头节点,让head指向新节点p。
(2)若原链表非空,则按节点值的大小(假设节点值已按升序排序)确定插入新节点的位置。若在头节点前插入新节点,则将新节点的指针域指向原链表的头节点(p->next=head),且让head指向新节点(head=p)。
(3)若在链表中间插入新节点,则将新节点的指针域指向下一节点(p->next = pr->next),且让前一节点的指针域指向新节点(pr->next = p)。
(4)若在表尾插入新节点,则末节点指针域指向新节点(pr->next = p)。