注意销毁链表时,要一个一个地free ( p )
下面各个函数均测试过可以正常运行
LinkList HeadInsert(LinkList &L);//头插法建立单链表
LinkList TailInsert(LinkList &L);//尾插法建立单链表
bool InitList(LinkList &L);//初始化单链表
bool Empty(LinkList L);//判空
int Length(LinkList L);//求表长
bool ListInsert(LinkList &L,int i, int e);在位序i处插入e
bool InsertNextNode(LNode p, int e);//后插操作 在p结点之后插入元素e
bool InsertPrior(LNode *p, int e);//前插操作, 在结点p之前插入元素e
bool ListDelete(LinkList &L, int i, int &e);//按位序删除(带头结点) 删除L的第i个元素,并用e返回被删除的值
bool DeleteNode(LNode *p);删除指定结点p, O(1) “偷天换日”
bool IndexLeagal(LinkList L,int i);//位序i的合法性检查,线性表的i从1开始
LNode GetElem(LinkList L,int i);//按位查找,返回第i个元素(带头结点)
LNode LocateElem(LinkList L,int e);//按值查找,找到数据域等于e的结点 O(n)
void PrintList(LinkList L);//打印链表
//线性表的链式表示 单链表 带头结点
#include<stdio.h>
#include<stdlib.h>//malloc free函数的头文件
typedef struct LNode{
int data;//数据域
struct LNode *next;//指针域
}LNode,*LinkList;
//************************ 函数 **************************
LinkList HeadInsert(LinkList &L);//头插法建立单链表
LinkList TailInsert(LinkList &L);//尾插法建立单链表
bool InitList(LinkList &L);//初始化单链表
bool Empty(LinkList L);//判空
int Length(LinkList L);//求表长
bool ListInsert(LinkList &L,int i, int e);//在位序i处插入e
bool InsertNextNode(LNode *p, int e);//后插操作 在*p结点之后插入元素e
bool InsertPrior(LNode *p, int e);//前插操作, 在结点p之前插入元素e
bool ListDelete(LinkList &L, int i, int &e);//按位序删除(带头结点) 删除L的第i个元素,并用e返回被删除的值
bool DeleteNode(LNode *p);//删除指定结点p, O(1) "偷天换日"
bool IndexLeagal(LinkList L,int i);//位序i的合法性检查,线性表的i从1开始
LNode *GetElem(LinkList L,int i);//按位查找,返回第i个元素(带头结点)
LNode* LocateElem(LinkList L,int e);//按值查找,找到数据域等于e的结点 O(n)
void PrintList(LinkList L);//打印链表
/*注意:
头插法时要malloc新结点
*/
//带头结点
//头插法建立单链表
LinkList HeadInsert(LinkList &L){//@@@
LNode *s;
int x;
L=(LinkList)malloc(sizeof(LNode));
//养成习惯,初始为空链表,否则此指针会指向某未知区域,可能会造成不知名错误
L->next=NULL;
printf("input node value(input 9999 to end):");
scanf("%d",&x);
while(x!=9999){
s=(LNode*)malloc(sizeof(LNode));//@@@
s->data=x;
s->next=L->next;
L->next=s;
printf("input next node value:");
scanf("%d",&x);
}
return L;
}
//头插法尾插法的核心:初始化操作,指定结点的后插操作
//尾插法建立单链表
//尾插法建立单链表
/*while{
每次取一个数据元素e;
ListInsert(L,length+1,e);//但这样的时间复杂度为n^2
//因为每第i次插入,ListInsert中的while循环都要执行i-1次
length++;
}
*/
LinkList TailInsert(LinkList &L){
int x;
L=(LinkList)malloc(sizeof(LNode));
//L->next=NULL;
LNode *s,*r=L;//r为表尾指针,s用来申请新结点
printf("input node value(input 9999 to end):");
scanf("%d",&x);
while(x!=9999){
//注意这里的s不能少,因为s用来申请新的结点,
//装入新的值,而后再接到r后面,然后再将r指向s,即尾结点
s=(LNode *)malloc(sizeof(LNode));
s->data=x;
// s->next=r->next;
r->next=s;//在结点r之后插入节点s
r=s;//r指向新的表尾结点,永远保持r指向最后一个节点
printf("input next node value:");
scanf("%d",&x);
}
r->next=NULL;//尾结点指针置空
return L;
}
//初始化单链表
bool InitList(LinkList &L){
L=(LNode*)malloc(sizeof(LNode));//分配一个头结点
//LNode* 等价于LinkList
//只是前者强调这是个结点,后者强调这是个链表
if(L==NULL){
printf("malloc失败\n");
return false;
}
L->next=NULL;
//这里的L为一个指向链表的指针,所以引用结构体成员时用->而非.
return true;
}
bool Empty(LinkList L){
return L->next==NULL;//与不带头结点的判空操作不一样
}
//求表长
int Length(LinkList L){
int len=0;//@@@
LNode *p=L->next;
while(p){
p=p->next;
len++;
}
return len;
}
//在位序i处插入e
bool ListInsert(LinkList &L,int i, int e){
int len = Length(L);
if(i<1||i>len+1){
printf("非法位序\n");
return false;
}
/*
LNode *p;//指针p指向当前扫描到的结点
int j=0;//当前p指向的是第几个结点
p=L;//L指向头结点,头结点算第0个结点,不存数据
//@@@ p->next != NULL
while(p->next != NULL && j < i-1){//循环找到第i-1个结点
p=p->next;
j++;
}
*/
//则GetElem的要求是位序不能<0,即返回的p可能是头结点
LNode *p=GetElem(L, i-1);//找到第i-1个结点,体会封装的好处
/*
LNode *s=(int *)malloc(sizeof(int));
s->data=e;
s->next=p->next;
p->next=s;
return true;//插入成功
*/
//可以调用函数InsertNextNode来实现上述代码功能
return InsertNextNode(p,e);
}
//后插操作 在*p结点之后插入元素e
bool InsertNextNode(LNode *p, int e){
if(p==NULL){
printf(" p == NULL !!!\n");
return false;
//在ListInsert中会调用,而ListInsert中的p可能返回的是NULL,
//则调用此函数的时候就需要用到这段代码
}
LNode *s=(LNode *)malloc(sizeof(LNode));
if(s == NULL){
printf("malloc failuer\n");
return false;//内存已满,空间分配失败
}
s->data=e;
s->next=p->next;
p->next=s;//将结点s连接到p之后
return true;
}
//前插操作, 在结点p之前插入元素e
//如果不给出头结点,则无法找到p的前驱结点 O(n)
//但是可以用偷天换日的方法实现前插 O(1)
bool InsertPrior(LNode *p, int e){
if(p==NULL){
printf("p==NULL\n");
return false;
}
LNode *s=(LNode*)malloc(sizeof(LNode));
if(s==NULL){
printf("malloc failure\n");
return false;
}
s->data=p->data;//新结点s插入到p之后,把p中的元素复制到s中,
s->next=p->next;
p->next=s;
//用e覆盖p中的元素,相当于前插了
p->data=e;//偷天换日,用后插法来实现前插法
return true;
}
//按位序删除(带头结点) 删除L的第i个元素,并用e返回被删除的值
bool ListDelete(LinkList &L, int i, int &e){
int len=Length(L);
if(i<1||i>len){
printf("非法位序\n");
return false;
}
/*
int j=0;//当前p指向的是第几个结点
LNode *p=L;//指针p指向当前扫描到的结点//L指向头结点,头结点是第0个结点(不存数据)
while(p->next!=NULL && j<i-1){
p=p->next;
j++;
}*/
LNode *p = GetElem(L, i-1);//找到第i-1个结点,
//体会封装的好处,避免重复代码,简洁,易维护
LNode *q=p->next;//令q指向被删除结点
e=q->data;//用e返回被删除元素的值
p->next=q->next;//将q结点从链中断开
free(q);//释放结点的存储空间
q=NULL;//避免野指针
return true;//删除成功
}
//删除指定结点p, O(1) "偷天换日"
/*
但是若p本身为最后一个节点,即p->data为NULL
则下面的代码有bug, (但是考试的时候可以这样写)
则需要老老实实地从头指针以此找到对于的前驱结点
*/
bool DeleteNode(LNode *p){
if(p==NULL){
printf("p==NULL\n");
return false;
}
if(p->next==NULL){//p是最后一个节点
printf("需要从头遍历找到p的前驱结点\n");
return false;
}
LNode *q=p->next;//令q指向p的后继结点
p->data=q->data;//和后继结点交换数据域
p->next=q->next;//将q结点从链中断开
free(q);//释放q结点的存储空间
q=NULL;
return true;
}
//DeleteNode的补充
bool DeleteNode2(LinkList &L,LNode *p){
if(p==NULL){
printf("p==NULL\n");
return false;
}
if(p->next == NULL){//说明p是最后一个结点,则需要从头开始遍历
LNode* q=L;
while(q->next != p){
q=q->next;
}
q->next=p->next;
free(p);
p=NULL;
return true;
}
else{//p是不是最后一个结点
//可用偷天换日,进行数据交换,删除后面的一个结点
LNode *q = p->next;
p->data=q->data;
p->next=q->next;
free(q);
q=NULL;
return true;
}
}
//位序i的合法性检查,线性表的i从1开始
bool IndexLeagal(LinkList L,int i){
int len=Length(L);
if(i<1 || i>len){
printf("i<1 || i>L.length\n");
return false;
}
return true;
}
//按位查找,返回第i个元素(带头结点) 可以查找第0个元素
LNode *GetElem(LinkList L,int i){
/*if(!IndexLeagal(L,i+1)){
return NULL;//把头结点看做第0个结点
}*/
int len=Length(L);
if(i<0 || i>len){
printf("i<0 || i>L.length\n");
return NULL;
}
int j=0;//当前p指向的是第几个结点
LNode *p=L;//指针p指向当前扫描到的结点
//L指向头结点,头结点是第0个结点,不存数据
while(p->next!=NULL && j<i){
p=p->next;
j++;
}
return p;
//当i值非法时,返回NULL
}
//按值查找,找到数据域等于e的结点 O(n)
LNode* LocateElem(LinkList L,int e){
LNode *p=L->next;//从第1个接地那开始查找数据域为e的结点
//这里注意如果e 的类型是结构体类型,应将里面的元素一一比较,不能直接用结构体名进行比较
while(p && (p->data != e)){
p=p->next;
}
return p;//找到后返回指向此结点的指针,否则返回NULL
}
//打印链表
void PrintList(LinkList L){
LNode *p=L->next;
int len=Length(L);
printf("(length:%d)List is:",len);
while(p){
printf("%d ",p->data);
p=p->next;
}
}
//销毁线性表,并释放内存空间
void DestroyList(LinkList &L){
/*
LinkList p=L;
while(p){//这里销毁线性表,要一个结点一个结点地free,不能认为p指向头结点了所以free(p)就好,这种是在malloc的时候
L=L->next;
free(p);
p=L->next; 这里应该是p=L;
}*/
LinkList p=L;
L=L->next;
printf("\nfree head\n");
free(p);//删除头结点
p=L;
while(p){//这里销毁线性表,要一个结点一个结点地free,不能认为p指向头结点了所以free(p)就好,这种是在malloc的时候
L=L->next;
printf("free %d \n",p->data);
free(p);
p=L;
}
}
void test(){
LinkList L;//声明一个指向单链表的指针,注意此处并没有创建一个链表
InitList(L);
PrintList(L);
printf("\n调用ListInsert\n");
for(int i=0;i<5;i++){
ListInsert(L,i+1, i+1);
}
PrintList(L);
LNode *p=GetElem(L,4);
printf("\np->data=%d\n",p->data);
printf("\n调用InsertNextNode\n");
InsertPrior(p,30);
PrintList(L);
p=LocateElem(L, 5);
if(p){
printf("\n找到了:p->data=%d\n",p->data);
}
else{
printf("\n不存在此数\n");
}
/*
printf("\n头插法:\n");
HeadInsert(L);
PrintList(L);
printf("\n销毁线性表\n");
DestroyList(L);
PrintList(L);
printf("\n尾插法:\n");
TailInsert(L);
PrintList(L);
printf("\n销毁线性表\n");
DestroyList(L);
PrintList(L);
*/
}
int main(){
test();
}