数据结构之单链表
1.知识准备
什么是结构体?
通俗的讲,结构体是用来描述某种事物所含属性的集合。 假如现在你需要定义一个描述学生某门课程成绩的结构体。那么这个结构体应该含有学号、姓名、课程名称、该门课程的成绩等基本信息。
struct student{
int id;//学号
char name[20];//姓名
float score;//成绩
char course[20];//课程名称
}
如何定义结构体?
定义结构体的一般形式为:
struct struct_tag //结构体标志(即类型)
{
member definition; //成员变量
}structure variables; //此结构体声明的变量
如何使用结构体?
结构体变量在使用上跟简单的数据类型变量(如 int)使用上并无太大区别!比如你可以使用
int *p
来定义个指向int类型的指针P。同样的,你也可以使用相似的方式来来定义一个指向结构体类型的指针。如:typedef struct student *stu
。
2.单链表
有的时候,处于内存中的数据并不是连续的。那么这时候,我们就需要在数据结构中添加一个属性,这个属性会记录下面一个数据的地址。有了这个地址之后,所有的数据就像一条链子一样串起来了,那么这个地址属性就起到了穿线连结的作用。
相比较普通的线性结构,链表结构的优势是什么呢?我们可以总结一下:
- 单个节点创建非常方便,普通的线性内存通常在创建的时候就需要设定数据的大小
- 节点的删除非常方便,不需要像线性结构那样移动剩下的数据
- 节点的访问方便,可以通过循环或者递归的方法访问到任意数据,但是平均的访问效率低于线性表
单链表的基本定义:
typedef int ElemType;
struct LinkNode{
ElemType data;
struct LinkNode *next;
}; typedef struct LinkNode *LinkedList;
单链表创建之尾插法:
顾名思义,尾插法就是把新生成的元素插入到原有的元素后面。
void CreatQueueList(LinkedList *L,int n){
//生成头结点
(*L)=(LinkedList)malloc(sizeof(strcut LinkNode));
(*L)->next=NULL;
LinkedList p,q;
//把头结点信息赋值给指针q
q=*L;
for(int i=0;i<n;i++){
p=(LinkedList)malloc(sizeof(struct LinkNode));
//键入数据
scanf("%d",&p->data);
q->next=p;
//q后移
q=q->next;
}
}
单链表创建之头插法:
跟尾插法不同的是,每当有新节点生成时,总是要把新生成的节点放到原有节点的前面。如此一来,我们生成的第一个节点就是整个单链表的尾节点(其指针域位空),最后生成那个节点是链表的第一个节点。该算法的整体思想是:
- 生成头结点L,L->next=NULL
- 生成第一个节点p,把头结点的指针域(NULL)赋给p的指针域
- 把头结点L跟第一个节点p串起来,L->next=p;
- 生成第二个节点q
- 把第二节点放到第一个结点前面 q->next=L->next,头节点放到第二节点前面L->next=q。
//创建栈式单链表(头插法) n->创建几个节点
void CreatStackList(LinkedList *L,int n){
//申请空间
(*L)=(LinkedList)malloc(sizeof(strcut LinkNode));
(*L)->next=NULL;
LinkedList p;
printf("请输%d个数据",n);
for(int i=n;i>0;i--){
p=(LinkedList)malloc(sizeof(struct LinkNode));
//键入数据
scanf("%d",&p->data);
p->next=(*L)->next;
(*L)->next=p;
}
}
单链表的插入操作:
插入操作可以分为两种:一种是根据位置插入,一种是根据元素插入。
根据位置插入算法的思想如下:- 找到该位置的前一个元素
- 生成新节点s,s->data=”要插入的元素”
- 把新节点插入s到该位置前(此时链子断了)s->next=p->next;
- 修补链子,把后面的链子跟前面的链子接起来,p->next=s;
Status InsertByIndex(LinkedList L,int i,ElemType e){
LinkedList p=L,s;
int j;
//找到该位置的前一个元素
while(p->next&&j<i-1){
p=p->next;
j++;
}
//如果位置不合法
if(!p||j>i-1){
return FALSE;
}
s=(LinkedList)malloc(sizeof(struct LiLinkNode));
s->data=e;
s->next=p->next;
p->next=s;
return TURE;
}
两种插入算法的思想基本一致,此处不在一一赘述,直接上代码
Status InsertByElem(LinkedList L,ElemType ele,ElemType e){
LinkedList p=L,s;
while(p->next->data==ele){
s=(LinkedList)malloc(sizeof(struct LiLinkNode));
s->data=e;
s->next=p->next;
p->next=s;
}
return TRUE;
}
单链表的销毁
销毁链表基本思想如下:由于单链表的一切操作皆始于头指针,所以我们必须不断的销毁头结点和指定新的头结点来完成单链表的销毁操作。之所以这样,是因为如果我们直接销毁头结点的话,那么我们将找不到整个链表其他节点的具体地址,这样就无法完成对链表的销毁操作。算法如下:
- 定义一个用来遍历的节点p
- 将头结点的下一个节点指向p,p=L->next(先后指)。
- 销毁头节点L,free(L)(再销毁)。
- 将p重新定义为头结点(指定新的头结点),L=p。
void DestroyList(LinkList *L){
LinkedList p;
while(*L){
p=(*L)->next;
free(*L);
(*L)=p;
}
}
- 单链表的删除
删除单链表某个节点的思想如下:
- 找到该节点q的前驱p
- 让p指向q的后继
- 销毁q
Status LinkedListDelete(LinkedList L,int i,ElemType *e){
int j=0;
LinkList p=L,q;
while(p->next&&j<i-1)
{
p=p->next;
j++;
}
if(!p->next||j>i-1)
return ERROR;
*e=p->next->data;
p->next=p->next->next;
free(p->next;)
}