链表(期末的时候可以稍微看看)

链式存储结构:任意的存储单元来存放线性表的数据元素。
这一组可以是连续的也可以是不连续的,甚至是零散分布在内存中的任意位置上的。
链表中的元素的逻辑次序和物理次序不一定相同。
在存储某一个位置的时候,还要存储下一个节点的位置,所以,一个节点需要有一个指针域和一个数据域。第一个元素的地址叫做头指针,最后一个结点就没有指针域了,指针域里直接写NULL
结点:数据元素的存储映像。有指针域和数据域两个部分组成
链表:n个结点由指针链组成一个链表。
单链表:结点只有一个指针域的链表,也可以叫做线性链表。
双链表:结点有两个指针域的链表。
循环链表:首尾相接,最后一个结点的指针域存储的是第一个元素的地址,也可以分为单循环链表和双循环链表。
链表有几种形式
1.不带头结点的链表:
表示空表:头指针为空
2.带头结点的链表
表示空表:看指针域是否为空
带头结点的好处:
1.便于首元结点的处理
2.便于空表和非空表的统一处理
头结点的数据域可以为空。
链表:顺序存取,但是存储位置是任意的
顺序表:任意存取,但是存储位置是顺序的
单链表:由表头唯一确定,因此单链表可以用头指针的名字来命名,若头指针名为L,则把链表称为表L,结构体里面一般写ElemType和next,next是指针型,存放着结点的地址。
例如:存储学生学号,姓名,成绩的单链表结点类型如下:
struct student{
char num[8];
char name[8];
int score;
struct student* next;}
typedef struct student* list;
单链表上基本操作的实现:
1.构造一个空的单链表:
(1.)生成新的结点作为头结点,用作头指针L指向头结点。
(2.)将头结点的指针域置空。
判断一个链表是否为空:
判断头指针的指针与是否为空。
单链表的销毁:
从头指针开始,依次释放
使用free§就可以删除了
Lnode* p;
while(L!=NULL){
p=L;
L=L->next;
free§;
}
清空链表:链表仍存在,但链表中无元素,成为空链表(头指针和头结点仍然存在)思路:依次释放所有的结点,并将头指针设置为空。
p->L->next
因为要保留头结点,所以p指向的应当是链表的首元结点
结束条件:p==NULL
所以,代码如下:

Lnode* p=L->next;
while(p!=NULL){
q=p->next;
delete p;
p=q;
}
L->next=NULL;
return 0;

求链表的表长:

int count;
Lnode* p;
p=L->list;
while(p!=NULL){
count++;
p=p->next;
}

删除节点
算法步骤:删除第i个节点
1.首先:找到i-1的存储位置P,保存要删除的a的值(如果有需要的话)
2.p->next就是要删除的位置的地址i+1的指针是p->next=p->next->next;
3.释放掉节点a的空间(也就是节点i的空间)利用free()函数就可以了。
具体实现:
Status ListDelete(LinkList &L,int i,ElemType &e){
p=L;j=0;
while(p->next&&j<i-1){p=p->next;++j;}寻找到第i个节点,并令p指向该前驱。
if(!(p->next)||j>i-1)return ERROR;如果p->next是空或者j的位置还比i靠后,那么删除的位置不合理
q=p->next;临时保存一下被删除的节点的地址地址用来做释放的准备。
p->next=q->next;改变指针域,或者可以直接这样:p->next=p->next->next;
e=q->data;用e来保存一下被删除节点的数据域。
delete q;把q释放掉。
return OK;}
分析算法效率:
首先是查找,从头开始找,遇到匹配的就停,所以执行次数最多的应该是将指针往后移的过程,所以算法的效率为O(n);
其次是插入和删除,因为链表不需要移动元素,只需要修改指针,一般情况下时间复杂度为O(1)
但是,如果进行前插或删除操作,则需要从头查找,所耗时间点为O(n)
建立单链表有两种方法,头插法和尾插法。
顾名思义就是插入到链表的头部或者链表的尾部
头插法:从一个空表开始,重复读入数据。
生成新结点,将读入数据放入到新节点的数据域中
从最后一个节点开始,依次将各节点插入到链表的前端。
一般用尾插法会比较方便
尾插法:从一个空表L开始,将新节点逐个插入到链表的尾部。
1.初始时,r与L均指向头结点,每读入一个数据元素则申请一个新节点,将新节点插入到尾部、结点后,r指向新节点。
正位序输入n个元素的值

void CreateList(LinkList &L,int n)
L=(List)malloc(struct List);
L->next=NULL;
r=L;
for(i=0;i<n;i++){
p=(List)malloc(struct List);
scanf("%d",&p->data);
p->next=NULL;
r->next=p;
r=p;
}

//链表初始化

void createList(list L,int n){
    list r=L;
    int a;
    for(int i=0;i<n;i++){
        scanf("%d",&a);
        list p=(list)malloc(sizeof(struct List));
        p->data=a;
        p->next=NULL;
        r->next=p;
        r=p;
    }
}

int main(){
    list L=(list)malloc(sizeof(struct List));
    int a=5;
    createList(L,a);
    return 0;
}

可以初始化一个链表。
链表的查找:按值查找,根据数据元素获取该数据所在的位置(也可以是序号):该数据的地址。
例如:查找30和15:

int main(){
list l1=(list)malloc(sizeof(struct LList));
int count=1;
creat(l1);
while(l1->next!=NULL){
    if(l1->data==15){
        printf("%d",count);
        break;
    }else{
    l1=l1->next;
    count++;
    }
}

我总是忘记在主函数里申请内存,会导致运行不起来,下次一定注意。
循环链表的特点:从表中任一结点出发均可以找到表中其他的结点。
由于循环链表中没有空指针,所以设计遍历操作的时候,它的终止条件是不像非循环链表那样判断是否为空,而是判断它们是否等于头指针。P->next!=L 这就是链表的循环条件。
如果经常要经常操作a1和an,顺着单链表来查找会比较不方便,所以可以通过尾指针来查找,会方便很多。
如何将带尾指针的链表合并:将第一个链表的尾结点的next域指向第二个链表的第一个结点(不是只存储指针域的头结点!)。
操作逻辑:
1.P存表头结点
2.tb表头连接到ta表尾
3.释放tb表头结点
4.修改指针
算法:
list He(list a,list b){
list p;
p=a->next;//p指向a的表头结点
a->next=b->next->next;//b的表头指向a的表尾,b也从表尾开始
free( b->next);
return b;
}
时间复杂度就是O(1)
双向链表:
单链表找后继结点非常方便,但是找他的前驱结点就会非常复杂,O(n)
双向链表就是在每个结点里在增加一个指针域,指向它的前驱结点,这样表中就形成了两个方向不同的链,所以是双向链表。
//双向链表
struct Slist{
int data;
struct Slist* next,*prior;//一个前驱,一个后继
};
双向链表的结构体
第一个节点没有前驱,只有后继
最后一个结点只有前驱,没有后继
空表就是前驱结点和后继结点都是空
双向循环链表:
1.让头结点的前驱指针指向链表的最后一个结点
2.让最后一个结点的后继指针指向头结点。
空表就是让前驱指针和尾指针直向自己。
双向链表结构的对称性(设指针P指向某一个结点)
p->prior->next=p=p->next->prior
双链表的插入操作:

void cha(list a,list b,list s){
    list p=(list)malloc(sizeof(struct));
    p=b;
    s->prior=p->prior;
    p->prior->next=s;
    s->next=p;
    p->prior=s;
}

先操作要插入结点的前驱(两个,一个由插入的指向原来的(插入的前驱),一个由原来的指向插入的(将插入的变成原来的后继)。
在操作要插入结点的后继(两个,同上)
双向链表的删除操作(删除掉一个结点):
顺序表和链表的比较:
链式存储结构:
不占有连续的存储空间
结点可以动态动态额度申请和释放
删除和插入的时候不需要移动数据元素。
链式存储的缺点:
存储密度小,指针域需要占用额外的存储空间。
存数密度:结点数据(data)本身占用的空间/结点占用的空间总量
存储密度大的空间利用率高,顺序表的存储密度为1,链表的存储密度小于1
链式存储结构是非随机存取结构,对任一结点的操作都要从头指针依指针链查找该结点,增加了算法复杂度。
顺序表和链表该怎么选:顺序表:表长范围不大,并能事先确定变化的范围,很少进行插入删除操作。一般用于访问数据元素。
链表:1.长度变化较大2.频繁进行插入或删除操作。
稀疏多项式:多项式非零项的数组表示,存放多项式的系数和指数。
创建一个新数组c,分别遍历比较a和b的每一项
指数相同,对应系数相加,若其和不为0,则在c中增加一个新项,系数是两个系数相加,指数不变
指数不相同:将指数较小的项复制到c中
遍历遍历!!!每一个都要和第二个的每一个比较一次。
数组C的长度要多大合适?因为是顺序存储,存储空间分配不灵活,运算的空间复杂度高。
所以可以用链式存储空间来存储这个问题,结构体中有两个数据域,一个系数域,一个指数域
怎样把若干项变成一个单链表:
1.创建一个只有头结点的空链表
2.根据多项式的项数n,循环n次执行以下操作:
1.生成一个新结点s
2.输入多项式当前项的系数和指数赋给新结点
s的数据域
3.设置一个前驱指针pre,用于指向指向待找到的第一个大于输入项指数的结点的前驱,pre初值指向头结点;
4.指针q初始化,指向首元结点
5.循链向下逐个比较链表中当前结点与输入项指数,找到第一个大于输入项指数的结点q
6.将输入项结点
s插入到结点*q之前
案例:图书信息管理系统
可以选择链表存储,也可以用顺序表来存储

  • 15
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值