线性表的链式存储结构
在每个节点中除包含有数据域外,只设置一个指针域 ,用以指向其后继节点,这样构成的链接表称为线性单向链 接表,简称单链表,为了便于插入和删除运算的实 现,每个链表带有一个头节点,并通过头节点的指针唯一标识该链表。
单链表的缺点:当访问过一个节点后,只能接着访问它的 后继节点,而无法访问它的前驱节点。
单链表的相关运算
先定定义一下链表的结构体
typedef struct Lnode{
int data;
struct Lnode *next;
} LinkList;
插入和删除节点
-
插入节点
LinkList *p //这是指向原来在链表中的 LinkList s //这是需要被插入的节点 s->next = p->next; p->next = s; //:只需修改相关节点的指 针域,不需要移动节点
-
删除节点
LinkList *p// 指向链表 假设需要被删的元素是指 的后驱元素 p->next = p->next->next;
建立单链表:
-
头插法建表
特点:新节点插入到当前链表的表头上,最后得到的链表是一个与插入顺序相反的链表void CreateHeadList(LinkList *&L,int a[],int n){//头插法建表 LinkList *s;//指向LinkList类型数据的指针 int i; L = (LinkList*)malloc(sizeof(LinkList)); L->next = NULL;//创建头节点,他的下一个是,UNLL for(i = 0;i<n;i++){ s = (LinkList*)malloc(sizeof(LinkList));//分配地址后,就是指针型变量; //这里 给s分配地址要写在for循环里面,每次分配新的才是新的节点。 s->data =a[i]; s->next = L->next;// L->next = s; // free(s); 也不可以释放,不然你刚创建的节点就没有了; } }
-
尾插法建表:
特点:生成的链表中节点的 次序和原数组元素的顺序一样。
void CreateEndList(LinkList *&L,int a[],int n){ LinkList *s,*r; int i; L = (LinkList*)malloc(sizeof(LinkList));//创建头节点 r = L//r时钟指向尾节点,开始的时候,指的是头节点 for(i = 0;i<n;i++){ s = (LinkList*)malloc(sizeof(LinkList)); s->data = a[i]; r->next = s; r=s; } r->next = UNLL;//尾节点的next为UNLL }
线性表基本运算在单链表上的实现
-
初始化线性表 InitList(L)
void InitList(*&L){ L = (LinkList*)malloc(sizeof(LinkList)); L->next = NULL;//不管是头插还是尾插,都一样, //尾插的话后面就改变了,而头插则一直都是UNLL。 }
-
销毁线性表 DestroyList(L)
void DistroyList(*&L){ LinkList *pre = L,*p = L->next; while(p!=NULL){ free(pre); pre = p; p = p->next;//遍历整个链表,释放每个节点 } free(pre);//还有一个没被放 }
-
判线性表是否为空表 ListEmpty(L)
bool ListEmpty(*L){ return (L->next=NULL); }
-
求线性表的长度 ListLength(L)
int ListLenth(*L){ int n=0; LinkList *p=L; while(L!=NULL){ n++; p=p->next;//p移向下一个节点 } return (n); }
-
输出线性表 DispList(L)
void DispList(LinkList *L) { LinkList *p=L->next; //p指向开始节点 while (p!=NULL){ //p不为NULL,输出*p节点的data值 printf("%d ",p->data); p=p->next; //p移向下一个节点 } printf("\n"); }
-
求线性表 L 中指定位置的某个数据元素 GetElem(L, I , e)
bool GetElem(*L,int i,int &e){//e来保存该元素的值 LinkList *p=L; for(j=0;j<i&&p!=NULL;j++){ p=p->next; } if(p==NULL) return false; else{ e = p->data; return true; } }
-
按元素值查找 LocateElem(L,e)
int LocateEleme(*L,int e){ int i=1; LinkList *p=L->next; while(p->data != e&& p!=NULL){ i++; p = p->next; } if(p==NULL) return(0); else return (i); }
-
插入数据元素 ListInsert(L,i,e)
bool ListInsert(*&L,int i,int e){ LinkList *p=L,*s; int j=0; while(j<i-1&&p!=NULL){//因为单链表只能由前去元素道后驱元素, // 所以如果想要在 第3个位置插要先找到第2个 j++; p=p->next; } if(p==NULL) return false; else { s=(LinkList *)malloc(sizeof(LinkList)); s->data=e; s->next = p->next; p->next = s; return true; } }
-
)删除数据元素 ListDelete(L,i,e)
bool ListDelete(LinkList *&L,int i;&e){ LinkList *p=L,*s; int j = 0; while(j<i-1&&p!=NULL){ j++; p = p->next } if(p==NULL) return false; else{ s=p->next; if(s==NULL) return false; else{ e=s->data; p->next = s->next; free(s);//记得释放,是个好习惯 } } }
一道例题
只有上面讲解好像还不理解,把代码合起来看看
有 一 个 带 头 节 点 的 单 链 表 L={a1,b1,a2,b2, …,an,bn} ,设计一个算法将其拆分成两个带头节点的单链表 L1 和 L2 : L1={a1,a2,…,an} , L2={bn,bn-1,…,b1} 要求 L1 使用 L 的头节点。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
typedef struct Lnode{
int data;
struct Lnode *next;
} LinkList;
void CreateHeadList(LinkList *&L,int a[],int n){//头插法建表
LinkList *s;//指向LinkList类型数据的指针
int i;
L = (LinkList*)malloc(sizeof(LinkList));
L->next = NULL;//创建头节点,他的下一个是,UNLL
for(i = 0;i<n;i++){
s = (LinkList*)malloc(sizeof(LinkList));//分配地址后,就是指针型变量;
//这里 给s分配地址要写在for循环里面,每次分配新的才是新的节点。
s->data =a[i];
s->next = L->next;//
L->next = s;
// free(s); 也不可以释放,不然你刚创建的节点就没有了;
}
}
//销毁线性表
void DistroyList(LinkList *&L){
LinkList *pre = L,*p = L->next;
while(p!=NULL){
free(pre);
pre = p;
p = p->next;//遍历整个链表,释放每个节点
}
free(pre);//还有一个没被放
}
//判断线性表是否为空
bool ListEmpty(LinkList *L){
return (L->next=NULL);
}
//求线性表的长度
int ListLenth(LinkList *L){
int n=0;
LinkList *p=L;
while(L!=NULL){
n++;
p=p->next;//p移向下一个节点
}
return (n);
}
//输出线性表
void DispList(LinkList *L)
{ LinkList *p=L->next; //p指向开始节点
while (p!=NULL){ //p不为NULL,输出*p节点的data值
printf("%d ",p->data);
p=p->next; //p移向下一个节点
}
printf("\n");
}
//求线性表指定位置的一个数值
bool GetElem(LinkList *L,int i,int &e){//e来保存该元素的值
LinkList *p=L;
for(int j=0;j<i&&p!=NULL;j++){
p=p->next;
}
if(p==NULL)
return false;
else{
e = p->data;
return true;
}
}
//产找第一个值为 e 的元素的位置
int LocateEleme(LinkList *L,int e){
int i=1;
LinkList *p=L->next;
while(p->data != e&& p!=NULL){
i++;
p = p->next;
}
if(p==NULL)
return(0);
else
return (i);
}
//插入数据元素
bool ListInsert(LinkList *&L,int i,int e){
LinkList *p=L,*s;
int j=0;
while(j<i-1&&p!=NULL){//因为单链表只能由前去元素道后驱元素,所以如果想要在 第3个位置插要先找到第2个
j++;
p=p->next;
}
if(p==NULL)
return false;
else
{
s=(LinkList *)malloc(sizeof(LinkList));
s->data=e;
s->next = p->next;
p->next = s;
return true;
}
}
void run(LinkList *L,LinkList *&L1,LinkList *&L2){
// L1要有尾插法,L2用头插法
LinkList *p=L->next,*r,*q;
L1 = L;//L1 用L这个头节点
r=L1;
L2 = (LinkList *)malloc(sizeof(LinkList));
L2->next = NULL;
while(p!=NULL){
r->next = p;
r=p;
//头插法完成
/*
q=p->next;
q->next = L2->next;
L2->next=q;
p=p->next->next;
//这个方法不行,这个虽然插好了,但是,如果要移动p的时候就不可以了
//因为p->next指的是q,但是q的next已经指的不是原来L里的下一个了
//这个主要是因为头插法改变插入元素的next值
*/
p=p->next;//把p往后移一个
q=p->next;
p->next = L2->next;
L2->next = p;
p=q;//这样就保证了P可以连续的在L中移动
}
r->next=NULL;
}
int main(){
int a[]={12,52,64,3,31,58,49,65,61,67,2,8,9,7,6,2,4,64};
int n = 18;
LinkList *L,*L1,*L2;
CreateHeadList(L,a,n);
DispList(L);
/*
int e = 18 ;
ListInsert(L,10,e);
DispList(L);
*/
run(L,L1,L2);
DispList(L1);
printf("***************\n");
DispList(L2);
printf("***************\n");
DispList(L)//这里发现,L打印出来和L1一样
}
单链表原地置逆
最进有伙伴还问单链表怎么原地置逆,我这里提供两种思路,第一中,修改一下 next指针,第二种,我们都知道,头插法得到的单链表是反的,所以我们可以原地头插法;
-
法一:有头节点的单链表。
void turn(LinkList *&L){ LinkList *p=NULL,*pre=NULL,*s=NULL; s = L->next; while(s!=NULL){ p = s; s = s->next; p->next = pre; pre = p; } L->next = pre; }
根据图中的示例,以此类推,知道s指向NULL就停止啦(撒花) -
原地头插法
void turn2(LinkList *&L){
LinkList *p,*s;
p = L->next;
L->next = NULL;
while(p!=NULL){
s = p->next;
p->next = L->next;
L->next = p;
p = s;
}
}
这个就和头插法比较类似了,就不具体画图解释了;