数据结构——线性表知识点回顾

一、线性表分类

存储方式:顺序存储——顺序表;链式存储——单链表、双链表、循环链表和静态链表

1、顺序表

逻辑上相邻、物理位置也相邻

顺序表的存储保密度高,每个节点只存储数据元素;

线性表的存储结构是一种随机存取的存储结构;

一个顺序表的所占用的存储空间大小与元素的存放顺序无关,与表的长度、元素的类型和元素各字段的类型有关;

线性表中元素的魏旭需

1.1 一维数组可以静态分配和动态分配

注意:动态分配并不是链式存储,属于顺序存储结构。分配的空间h大小可以在运行时决定

1.2 顺序表上的基本操作

(1)插入操作(1~n+1)

最好情况:在表尾插入,元素不在往后移动,时间复杂度O(1);

最坏的情况:在表头插入,元素后移将执行n次,时间复杂度O(n);

平均情况:随机插入,时间复杂度O(n);

因此,线性表插入算法的平均时间复杂度为O(n)。

(2)删除操作(1~n)

线性表删除算法的平均时间复杂度为O(n)。

(3)按值查找

线性表按值查找算法的平均时间复杂度为O(n)。

2.链表

不需要使用地址连续的存储单元

2.1单链表

线性表的链式存储;

链表节点除存放元素自身的信息外,还存放一个指向其后续的指针。

通常用头指针来表示一个单链表,头指针为Null时,表示这是一个空链表

在单链表的第一个节点之前附加一个结点,成为头结点。头结点的数据域不设信息,指针域指向线性表的第一个元素的结点。

头结点和头指针的区分:头结点有无,头指针始终指向链表的第一个结点,而头结点是带头结点链表中的第一个结点,结点内通常不存储信息。

优点:避免像顺序表一样需要大量的连续的存储空间,

缺点:但是单链表附加指针域,也存在浪费存储空间的现象

引入头结点的优点:由于开始结点位置被在存放头结点的指针域中,所以在链表的一个位置上的操作和其他位置上的操作一致,无需进行特殊处理;无论链表是否为空,其头指针是指向头结点的非空指针(空表中头结点的指针域为空)。因此,空表和非空表的处理就完全一致了。

2.1.1 单链表的基本操作实现

(1)采用头插法建立单链表L

//C语言实现的关键语句

//s为插入的结点,L为生成的链表

s->data=x;
s->next=L->next;
L->next=s;

采用头插法建立链表,读入数据的顺序和生成的链表中元素的顺序是相反的,每个结点插入时间为O(1),设单链表的长度为n,z则总的时间复杂度为O(n)。

(2)采用尾插法建立单链表

必须增加一个尾指针,使其指向当前链表的尾结点。时间复杂度和头插法相同。

//s为插入的结点,r 为尾指针

s->data=x;
r->next=s;
r=s;

(3)按序号查找结点值中从第一节点出发,顺指针next域逐个往下搜索,直到找到第 i个结点为止,否则返回最后一个结点指针域NULL。

//C语言代码实现
LNode *GetElem(LinkList L, int i){
LNode *p=L->next;
if(i==0)
    return L;
if (i<1)
    return NULL;
while(p&&jnext;
    j++;
}
return p;
}

 按序号查找操作的时间复杂度为O(n)

(4)按值查找表结点

从单链表第一个结点开始,由前往后依次比较表中各结点数据域的值,若某结点数据域的等于给定值e,则返回给定值e,则返回该结点的指针;若找不到这样的结点,则返回NULL。

//C语言实现
LNode *LocateELem(LinkList L,ElemType e){
    Lnode *p=L->next; //从第一个结点
    while (p!=NULL&&p->!=e)
        p=p->next;
    return p;
}

  按值查找操作的时间复杂度为O(n)

(5)插入结点操作

插入操作 是将值为x的新结点插入到单链表的第i个 位置上。先检查 插入位置的合法性,然后找到待插入位置的前驱结点,即第i-1个结点,在其后插入新的结点。

//按序号查找,找到i-1结点,然后插入新结点
p=GetElem(L,i-1);
s->next=p->next;
p->next=s;

以上为后插操作,前插操作与后插操作恰恰相反,可以将其转换成后插操作,前提是从单链表的头结点开始顺序查找到其前驱结点,时间复杂度为O(n),只进行数据域的交换就可以啦

//将*s 结点插入*p 之前的主要的代码片段
s->next=p->next;
p->next=s;
temp=p->data;
p->data=s->data;
s->data=temp;

(6)删除操作

删除操作是将单链表的第i个结点。先检查删除位置的合理性,然后查找表中第i-1个结点,即被删除结点的前驱结点,再将其删除。

//*p 为前驱结点,*q 为要删除的结点
p=GetElem(L,i-1);
q=p->next;

p->next=q->next;
free(q);

和插入算法一样,该算法的主要事件也是耗费在查找操作上,时间复杂度为O(n)。

扩展删除结点*p,可以通过删除*p的后续结点操作来实现,实质就是将其后续结点的值赋予其自身,然后删除后继结点,时间复杂度O(n)。

q=p->next;
p->data=p->next->data;
p->next=q->next;
free(q);

(7)求表长操作(不含有头结点)

算法实现的时间复杂度为O(n)

2.1.2 双链表

双链表仅在单链表结点中增长了一个指向一个其前驱的prior 指针,因此,在双链表中执行按值查找和按位查找的操作和单链表相同,但是在插入和删除操作的实现上,在单链表中有着较大的不同。需要对prior指针做出修改。

双链表很容易找到其前驱结点,因此,插入删除结点的算法的时间复杂度仅为O(1)。

(1)双链表的插入操作

在双链表中 p所指的结点之后插入结点*s。

s->next=p->next;
p->next->prior=s;
s->prior=p;
p->next=s;

(2)双链表的删除操作

 删除双链表中结点*p的后继结点*q;

p->next=q->next;
pq->next->prior=p;
free(q);

2.1.3循环链表

2.1.3.1 循环单链表

与单链表的区别在于表中最后一个结点的指针不是NULL,而改为指向头结点,从而整个链表形成一个环。

判断空链表的条件为:不是头结点是否为空,而是尾指针的next域是否等于头指针。

插入和删除操作无需判断是否为表尾

2.1.3.1 循环双链表

头结点的prior指针还要指向表尾结点

在循环双链表L中,某结点*p为结点时,p->next==L;当循环双链表我空表时,起头结点的prior域和next域都等于L。

2.1.4静态链表

静态链表是借助线性表的链式存储结构,结点也有数据域data和指针域next。但这里指的是结点的相对地址,和顺序表一样,静态链表也要预先分配一块连续的内存空间。

2.1.5 顺序表和链表的比较

(1)存储方式

顺序表可以顺序存储,也可以随机存取,链表只能从表头顺序存取元素。

(2)找、插入和删除操作

对于按值查找,当顺序表在无序的情况下,两者的时间复杂度均为O(n);而当顺序有序时,可采用折半查找,此时时间复杂度为O(log2n)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(算法,数据结构)