线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列等,线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构(存储结构)上并不一定是连续的,线性表在物理上存储时,通常以顺序表和链式结构的形式存储。
线性表的顺序存储—>顺序表
是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
线性表的链式存储
线性表中的数据结点在内存中的位置是任意的,即逻辑上相邻的数据元素在物理位置(内存存储的位置)上不一定相邻。
链式存储结构的优点:
空间利用率高需要一个空间就分配一个空间
数据元素的逻辑次序靠节点的指针来指示,插入和删除时不需要移动数据结点,任意位置插入删除时间复杂度为O(1)
链式存储结构的缺点:
存储密度小,每个结点的指针域需要额外占用存储空间。当每个结点的数据域所占字节不多时,指针域所占空间的比重显得很大,存储密度大空间利用率越大。
顺序表因为只有数据域,没有指针域所以它的存储密度为最大1
不过这个问题,一个结点也就多几个指针,最多8个字节,所以若是在现代计算机这点空间已经不算什么,不过如果是像单片机这种嵌入式设备内存较小,所以还是会有一点点影响的
链式存储结构是非随机存取结构,对任一结点的操作都要从头指针依次查找到该节点,算法复杂度较高。
顺序表的优点:
存储密度为1最高,因为没有指针域空间利用率高
随机存取,按位置访问元素的时间复杂度为O(1),直接根据数组下标访问元素
顺序表的缺点:
动态顺序表增容会付出一定性能消耗,其次可能还是会存在一定的空间浪费(不可能扩容的刚刚好)
在头部或者中部左右的插入删除,需要移动元素,时间复杂度为O(N),效率低。
顺序表的缺点就是链表的优点,而链表的缺点就是顺序表的优点,所以说不能说链表一定就比顺序表高级,我们要视情况而定。
二.单链表的介绍
线性表的链式存储
线性表中的数据结点在内存中的位置是任意的,即逻辑上是线性的数据元素在物理位置(内存存储的位置)上不一定相邻。
结点为什么在内存中是随机存储的呢
因为我们产生一个结点要给他分配内存是动态分配出来的(malloc),而分配的结点的内存的地址是随机的,所以结点的地址是随机的,也就是说结点在内存中的存储是随机的。
单链表的一个结点
链表基础
链表的优点
n个节点离散分配
每一个节点之间通过指针相连
每一个节点有一个前驱节点和一个后继节点
首节点没有前驱节点,尾节点没有后继节点
基本概念
首节点:存放第一个有效数据的节点
头节点:在单链表的第一个结点之前附设一个结点,它没有直接前驱,称之为头结点,头结点的数据域可以不存储任何信息,指针域指向第一个节点(首节点)的地址。头结点的作用是使所有链表(包括空表)的头指针非空
头指针:指向头节点的指针
尾节点:存放最后一个有效数据的节点
尾指针:指向尾节点的指针
创建链表
定义一个结构体
typedef struct linklist { int score;//定义数据域 struct student *next;//定义指针域 }linklist;
创建一个链表
//初始化一个链表 n为链表节点个数 Linklist *linklistcreate(int n) { Linklist *head,*node,*end;//定义头节点,普通节点,尾部节点; head = (Linklist*)malloc(sizeof(Linklist));//分配地址 end = head;//若是空链表,则头尾节点一样 for(int i =0;i<n;i++) { node = (Linklist*) malloc(sizeof(Linklist)); scanf("%d",&node->score); end->next = node; end=node; } end->next=NULL;//结束创建 return head; }
插入节点
//插入节点 //插入节点就是用插入前节点的指针域链接上插入节点的数据域,再把插入节点的指针域链接上插入后节点的数据域。 //e->next = head->next; head->next = e void insert(Linklist *list,int n) { Linklist *t=list,*in; int i =0; while(i<n&&t!=NULL) { t = t->next; i++; } if(t!=NULL) { in = (Linklist*) malloc(sizeof(Linklist)); puts("请输入要插入的值:"); scanf("%d",&in->score); in->next=t->next;//填充in节点的指针域,也就是说把in的指针域指向t的下一个节点 t->next=in;//填充t节点的指针域,把t的指针域重新指向in } else { puts("节点不存在"); } }
删除节点
//删除链表节点 //删除链表的元素也就是把前节点的指针域越过要删除的节点指向下下个节点。 // 即:p->next = q->next;然后放出q节点的空间,即free(q); void delete(Linklist *list,int n) { Linklist *t = list,*in; int i =0; while(i<n && t != NULL) { in = t; t = t->next; i++; } if(t != NULL) { in->next= t->next; free(t); } else { puts("节点不存在"); } }
修改节点
//修改链表节点值 void change(Linklist *list,int n) { Linklist *t = list; int i = 0; while (i<n&&t!=NULL) { t = t->next; i++; } if(t!=NULL) { puts("输入要修改的值:"); scanf("%d",&t->score); } else { puts("节点不存在"); } }
输出节点
void output(Linklist *head,int n) { Linklist *h = head; int i =0; while (h != NULL) { h =h->next; printf("%d",h->score); } }
完整代码
#include<stdio.h> #include <malloc.h> #include <stdbool.h> //定义结构体 typedef struct linklist{ int score;//定义数据域 struct student *next;//定义指针域 }Linklist; Linklist *linklistcreate(int n); void insert( Linklist *head,int n); void delete(Linklist *head,int n); void change(Linklist *head,int n); void output(Linklist *head,int n); int main(){ int n,in,x,y; char c; struct Linklist *head =NULL; printf("请输入要创建的节点个数:\n"); scanf("%d",&n); head = linklistcreate(n); printf(head); while (true){ printf("请选择要执行的操作:\n"); printf("1.插入节点\n2.删除节点\n3.修改节点\n4.退出\n"); scanf("%c",&c); if(c =='1'){ printf("你想要在哪插入节点:\n"); scanf("%c",&in); insert(head,in); printf(head); output(head,n); }else if(c == '2'){ printf("你想要删除哪个节点的数据:\n"); scanf("%d",&x); delete(head,x); printf(head); }else if(c =='3'){ printf("你想要修改哪个节点的数据:\n"); scanf("%d",&y); change(head,y); printf(head); }else if(c =='4'){ exit(0); } } } //初始化一个链表 n为链表节点个数 Linklist *linklistcreate(int n){ Linklist *head,*node,*end;//定义头节点,普通节点,尾部节点; head = (Linklist*)malloc(sizeof(Linklist));//分配地址 end = head;//若是空链表,则头尾节点一样 for(int i =0;i<n;i++){ node = (Linklist*) malloc(sizeof(Linklist)); scanf("%d",&node->score); end->next = node; end=node; } end->next=NULL;//结束创建 return head; } //插入节点 //插入节点就是用插入前节点的指针域链接上插入节点的数据域,再把插入节点的指针域链接上插入后节点的数据域。 //e->next = head->next; head->next = e void insert(Linklist *list,int n){ Linklist *t=list,*in; int i =0; while(i<n&&t!=NULL){ t = t->next; i++; } if(t!=NULL){ in = (Linklist*) malloc(sizeof(Linklist)); puts("请输入要插入的值:"); scanf("%d",&in->score); in->next=t->next;//填充in节点的指针域,也就是说把in的指针域指向t的下一个节点 t->next=in;//填充t节点的指针域,把t的指针域重新指向in } else{ puts("节点不存在"); } } //删除链表节点 //删除链表的元素也就是把前节点的指针域越过要删除的节点指向下下个节点。 // 即:p->next = q->next;然后放出q节点的空间,即free(q); void delete(Linklist *list,int n){ Linklist *t = list,*in; int i =0; while(i<n && t != NULL){ in = t; t = t->next; i++; } if(t != NULL){ in->next= t->next; free(t); } else{ puts("节点不存在"); } } //修改链表节点值 void change(Linklist *list,int n) { Linklist *t = list; int i = 0; while (i<n&&t!=NULL) { t = t->next; i++; } if(t!=NULL) { puts("输入要修改的值:"); scanf("%d",&t->score); } else { puts("节点不存在"); } void output(Linklist *head,int n) { Linklist *h = head; int i =0; while (h != NULL) { h =h->next; printf("%d",h->score); } }
链表相关练习
#include "ChainList.h" /*初始化链表*/ bool Init(ChainListType *head) { head = (ChainListType *)malloc(sizeof(ChainListType)); if(head == NULL) { printf("初始化失败!\n"); return false; } head->next=NULL; return true; } /*获取链表结点数量*/ int Length(ChainListType *head) { ChainListType *p; int i=0; p=head; while(p) //遍历整个链表 { i++; //累加结点数量 p=p->next; //处理下一结点 } return i; //返回结点数量 } /*添加结点到链表结尾*/ ChainListType *AddEnd(ChainListType *head,DATA data) { ChainListType *node,*p; if(!(node=(ChainListType *)malloc(sizeof(ChainListType)))) { printf("为保存结点数据申请内存失败!\n"); return NULL; //分配内存失败 } node->data=data; //保存数据 node->next=NULL; //设置结点指针为空,即为表尾 //是头指针 if(head==NULL) { head=node; return head; } //不是头指针 p=head; while(p->next!=NULL) //查找链表的末尾 p=p->next ; p->next=node; return head; } /*添加结点到链表首部*/ ChainListType *AddFirst(ChainListType *head,DATA data) { ChainListType *node; if(!(node=(ChainListType *)malloc(sizeof(ChainListType)))) { printf("为保存结点数据申请内存失败!\n"); return NULL; //分配内存失败 } node->data=data; //保存数据 node->next=head; //指向头指针所指结点 head=node; //头指针指向新增结点 return head; } /*插入结点到链表指定位置*/ ChainListType *Insert(ChainListType *head,char *findkey,DATA data) { ChainListType *node,*node1; if(!(node=(ChainListType *)malloc(sizeof(ChainListType)))) //分配保存结点的内容 { printf("为保存结点数据申请内存失败!\n"); return 0; //分配内存失败 } node->data=data; //保存结点中的数据 node1=Find(head,findkey); if(node1) //若找到要插入的结点 { node->next=node1->next; //新插入结点指向关键结点的下一结点 node1->next=node; //设置关键结点指向新插入结点 } else { free(node);//释放内存 printf("未找到插入位置!\n"); } return head;//返回头指针 } /*按关键字在链表中查找内容*/ ChainListType *Find(ChainListType *head,char *key) { ChainListType *p; p=head; //保存链表头指针 while(p) //若结点有效,则进行查找 { if(strcmp(p->data.key,key)==0) //若结点关键字与传入关键字相同 return p; //返回该结点指针 p=p->next; //处理下一结点 } return NULL; //返回空指针 } /*删除指定关键字的结点*/ bool Delete(ChainListType *head,char *key) { ChainListType *node,*p; //node保存删除结点的前一结点 node=p=head; while(p) { if(strcmp(p->data.key,key)==0) //找到关键字,执行删除操作 { node->next=p->next; //使前一结点指向当前结点的下一结点 free(p); //释放内存 return true; } else { node=p; //指向当前结点 p=p->next; //指向下一结点 } } return false; //未删除 } /*遍历链表*/ void ShowAll(ChainListType *head) { ChainListType *h; DATA data; h=head; printf("链表所有数据如下:\n"); while(h) //循环处理链表每个结点 { data=h->data; //获取结点数据 printf("(%s,%s,%d)\n",data.key,data.name,data.age); h=h->next; //处理下一结点 } return; } /*销毁链表*/ bool Destory(ChainListType *head) { ChainListType *p=NULL; if(head == NULL) return false; while(head) { p=head->next; free(head); head=p; } return true; }
#include <stdio.h> #include <stdlib.h> struct grade { int score; struct grade *next; }; typedef struct grade NODE; //typedef为C语言的关键字,作用是为一种数据类型定义一个新名字。 //使用typedef目的一般有两个,一个是给变量一个易记且意义明确的新名字, //另一个是简化一些比较复杂的类型声明。 struct grade *create(); //创建链表 void insert(NODE *head,NODE *pnew,int i); //插入链表 void pdelete(NODE *head,int i); //删除列表 void display(NODE *head); //输出链表 void Pfree(NODE *head); //销毁链表 int main(int argc, char *argv[]) { struct grade *head,*pnew; head=create(); if (head==NULL) return 0; printf("输出创建的链表:"); display(head); pnew=(NODE *)malloc(sizeof(NODE)); if (pnew==NULL) { printf("创建失败!"); return 0; } pnew->score=88; insert(head,pnew, 3); //将新节点插入节点3的后面 printf("插入后的链表:"); display(head); pdelete(head,3); //删除节点3 printf("删除后的链表:"); display(head); Pfree(head); return 0; } struct grade *create() { NODE *head,*tail,*pnew; int score; head=(NODE *)malloc(sizeof(NODE)); //创建头节点。 if (head==NULL) { //创建失败返回 printf("创建失败!"); return NULL; } head->next=NULL; //头节点指针域置NULL tail=head; // 开始时尾指针指向头节点 printf("输入学生成绩:"); while (1) { //创建链表 scanf("%d",&score); if (score<0) //成绩为负是退出循环 break; pnew=(NODE *)malloc(sizeof(NODE)); //创建新节点 if (pnew==NULL) { //创建失败返回 printf("创建失败!"); return NULL; } pnew->score=score; //新节点数据域存放输入的成绩 pnew->next=NULL; //新节点指针域置NULL tail->next=pnew; //新节点插入到表尾 tail=pnew; //为指针指向当前的尾节点 } return head; //返回创建链表的头指针 } void insert(NODE *head,NODE *pnew,int i) { NODE *p; //当前指针 int j; p=head; for (j=0; j<i&&p!=NULL; j++) //p指向要插入的第i个节点 p=p->next; if (p==NULL) { //节点i不存在 printf("与插入的节点不存在!"); return; } pnew->next=p->next; //插入节点的指针域指向第i个节点的后继节点 p->next=pnew; //犟第i个节点的指针域指向插入的新节点 } void pdelete(NODE *head,int i) { NODE *p,*q; int j; if (i==0) //删除的是头指针,返回 return; p=head; for (j=1; j<i&&p->next!=NULL; j++) p=p->next; //将p指向要删除的第i个节点的前驱节点 if (p->next==NULL) { //表明链表中的节点不存在 printf("不存在!"); return; } q=p->next; //q指向待删除的节点 p->next=q->next; //删除节点i,也可写成p->next=p->next->next free(q); //释放节点i的内存单元 } void display(NODE *head) { NODE *p; for (p=head->next; p!=NULL; p=p->next) printf("%d ",p->score); printf("\n"); } void pfree(NODE *head) { NODE *p,*q; p=head; while (p->next!=NULL) { //每次删除头节点的后继节点 q=p->next; p->next=q->next; free(q); } free(head); //最后删除头节点 } void Pfree(NODE *head) { NODE *p,*q; p=head; while (p->next!=NULL) { q=p->next; p->next=q->next; free(q); } free(p); }
#include <stdio.h> #include <malloc.h> #include <conio.h> #include <stdlib.h> //链表单元定义,链表相关变量 struct student { int id; float score; struct student *next; } *head,*pthis; //输入数据创建链表 void input() { struct student *tmp; printf("\n\n请输入学生的信息以学号为0结束:\n"); do { printf("ID\t成绩\n"); if ((tmp=(struct student *)malloc(sizeof(struct student)))==NULL) { printf("\n错误!不能申请所需的内存!\n"); exit(0); } scanf("%d\t%f",&tmp->id,&tmp->score); tmp->next=NULL; if (tmp->id!=0) { if (head==NULL) { head=tmp; pthis=head; } else { pthis->next=tmp; pthis=pthis->next; } } } while (tmp->id!=0); free(tmp); } //搜索链表找到第一个符合条件的项目输出 void search(int id) { printf("\n\n查询结果\n"); printf("ID\t成绩\n"); printf("-------------------------------\n"); if (head==NULL) { printf("\n错误!没有数据!\n"); return; } pthis=head; while (pthis!=NULL) { if (pthis->id==id) { printf("%d\t%.2f\n",pthis->id,pthis->score); return; } else { pthis=pthis->next; } } printf("\n没有找到!\n"); } //列表输出链表中的所有项 void list() { printf("\n\n数据列表\n"); printf("ID\t成绩\n"); printf("-------------------------------\n"); if (head==NULL) { printf("错误,没有数据!\n"); return; } pthis=head; while (pthis!=NULL) { printf("%d\t%.2f\n",pthis->id,pthis->score); pthis=pthis->next; } } //插入数据 void insert() { int i,p; struct student *tmp; if (head==NULL) { printf("\n\n数据不存在,无法插入!\n"); return; } printf("\n请输入插入点:\n"); scanf("%d",&p); if (p<0) { printf("输入不合法!"); return; } printf("\n\n请输入学生的信息:\nID\t成绩\n"); if ((tmp=(struct student *)malloc(sizeof(struct student)))==NULL) { printf("\n错误!不能申请所需的内存!\n"); exit(0); } scanf("%d\t%f",&tmp->id,&tmp->score); tmp->next=NULL; if (tmp->id!=0) { pthis=head; if (p==0) { tmp->next=head; head=tmp; } else { for (i=0; i<p-1; i++) { if (pthis->next->next==NULL) { printf("\n找不到插入点,您输入的数据太大!\n"); return; } pthis=pthis->next; } tmp->next=pthis->next; pthis->next=tmp; } } else { printf("\n数据无效!\n"); free(tmp); } } //追加数据 void append() { struct student *tmp; printf("\n\n请输入学生的信息:\nID\t成绩\n"); if ((tmp=(struct student *)malloc(sizeof(struct student)))==NULL) { printf("\n错误!不能申请所需的内存!\n"); exit(0); } scanf("%d\t%f",&tmp->id,&tmp->score); tmp->next=NULL; if (tmp->id!=0) { if (head==NULL) { head=tmp; } else { pthis=head; while (pthis->next!=NULL) { pthis=pthis->next; } pthis->next=tmp; } } else { free(tmp); printf("\n数据无效!\n"); } } //删除数据 void del() { int p,i; struct student *tmp; if (head==NULL) { printf("\n\n没有数据,无法删除!\n"); return; } printf("\n\n请输入要删除的记录号:\n"); scanf("%d",&p); if (p<0) { printf("\n输入不合法!\n"); return; } if (p==0) { pthis=head; head=pthis->next; free(pthis); pthis=head; } else { pthis=head; for (i=0; i<p-1; i++) { pthis=pthis->next; if (pthis->next==NULL) { printf("\n\n指定记录不存在,无法删除!\n"); return; } } tmp=pthis->next; pthis->next=pthis->next->next; free(tmp); } } //程序主函数 void main() { char command=0; int id=0; //主循环 do { printf("\n\n\t 菜单\n"); printf("-------------------------------\n"); printf("\ta,输入数据\n"); printf("\tb,查询记录\n"); printf("\tc,数据列表\n"); printf("\td,追加记录\n"); printf("\te,插入记录\n"); printf("\tf,删除记录\n"); printf("\tg,退出系统\n"); printf("-------------------------------\n"); printf("\t请选择:"); command=getch(); //命令处理 switch (command) { case 'a': if (head==NULL) { input(); break; } else { printf("\n\n数据已经存在!\n"); break; } case 'b': printf("\n\n要查询的ID:"); scanf("%d",&id); search(id); break; case 'c': list(); break; case 'd': append(); break; case 'e': insert(); break; case 'f': del(); break; } } while (command!='g'); }
#include "ChainList.h" int main() { ChainListType *node, *head=NULL; DATA data; char key[15],findkey[15]; //初始化链表 Init(head); printf(printf("该链表共有%d个结点。\n",Length(head)); //添加结点(尾部) printf("输入链表中的数据,包括关键字、姓名、年龄,关键字输入0,则退出:\n"); do { fflush(stdin); //清空输入缓冲区 scanf("%s",data.key); if(strcmp(data.key,"0")==0) break; //若输入0,则退出 scanf("%s%d",data.name,&data.age); head=AddEnd(head,data);//在链表尾部添加结点数据 }while(1); //返回结点数量 printf("该链表共有%d个结点。\n",Length(head)); ShowAll(head); //显示所有结点 //插入结点 printf("\n插入结点,输入插入位置的关键字:") ; scanf("%s",&findkey);//输入插入位置关键字 printf("输入插入结点的数据(关键字 姓名 年龄):"); scanf("%s%s%d",data.key,data.name,&data.age);//输入插入结点数据 head=Insert(head,findkey,data);//调用插入函数 //显示所有结点 ShowAll(head); //查找结点 printf("\n在链表中查找,输入查找关键字:"); fflush(stdin);//清空输入缓冲区 scanf("%s",key);//输入查找关键字 node=Find(head,key);//调用查找函数,返回结点指针 if(node)//若返回结点指针有效 { data=node->data;//获取结点的数据 printf("关键字%s对应的结点数据为(%s,%s,%d)\n" ,key,data.key,data.name,data.age); } else//若结点指针无效 printf("在链表中未找到关键字为%s的结点!\n",key); //删除结点: printf("\n在链表中删除结点,输入要删除的关键字:"); fflush(stdin);//清空输入缓冲区 scanf("%s",key);//输入删除结点关键字 Delete(head,key); //调用删除结点函数 ShowAll(head); //显示所有结点 //销毁链表 Destory(head); getchar(); return 0; }