数据结构:线性表(链表)

1、链表

(1)概念

  • 结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻
  • n 个结点由指针链组成一个链表。它是线性表的链式存储映像,称为线性表的链式存储结构

(2)结点组成

  • 数据域:存储元素数值数据
  • 指针域:存储直接后继结点的存储位置

(3)单链表、双链表与循环链表:

  • 结点只有一个指针域的链表,称为单链表或线性链表
  • 有两个指针域的链表,称为双链表。双向链表能够克服单链表查询前驱结点必须从表头出发的问题,双向链表有两个指针域分别指向自己的前驱结点和后继结点,查找的时间复杂度为O(1)
  • 首尾相接的链表称为循环链表,从循环链表中的任何一个结点的位置都可以找到其他所有结点,而单链表做不到 

(4)头指针、头结点和首元结点 

  • 头指针是指向链表中第一个结点的指针
  • 首元结点是指链表中存储第一个数据元素a1的结点
  • 头结点是在链表的首元结点之前附设的一个结点,数据域内只放空表标志和表长等信息。头结点的数据域可以为空,也可存放线性表长度等附加信息,但此结点不能计入链表长度值。

       

 (5)在链表中设置头结点有什么好处

  • 便于首元结点的处理:首元结点的地址保存在头结点的指针域中,所以在链表的第一个位置上的操作和其它位置一致,无须进行特殊处理
  • 便于空表和非空表的统一处理:无论链表是否为空,头指针都是指向头结点的非空指针,因此空表和非空表的处理也就统一了

 (6)特点

  • 结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻
  • 访问时只能通过头指针进入链表,并通过每个结点的指针域向后扫描其余结点,所以寻找第一个结点和最后一个结点所花费的时间不等

(7)优缺点

优点:

  • 数据元素的个数可以自由扩充
  • 插入、删除等操作不必移动数据,只需修改链接指针,修改效率较高

缺点:

  • 存储密度小
  • 存取效率不高,必须采用顺序存取,即存取数据元素时,只能按链表的顺序进行访问

(8)结构定义

typedef struct Lnode
{
     ElemType   data;       //数据域
     struct LNode  *next;   //指针域
}LNode,*LinkList;   // *LinkList为Lnode类型的指针

(9)初始化

Status InitList_L(LinkList &L){ 
   L=new LNode;  //生成新结点作头结点,用头指针L指向头结点。                      
   L->next=NULL; //头结点的指针域置空    
   return OK; 
} 

2、链表的基本操作

(1)销毁

Status DestroyList_L(LinkList &L)
{
    LinkList p;
       while(L)
        {
            p=L;  
            L=L->next;
            delete p;  
        }
     return OK;
 }

(2)清空

Status ClearList(LinkList & L){
  // 将L重置为空表 
   LinkList p,q;
   p=L->next;   //p指向第一个结点
   while(p)       //没到表尾 
   { 
q=p->next;
delete p;
p=q;
} L
->next=NULL; //头结点指针域为空 return OK; }

与销毁不同,清空的时候会保留头结点

(3)求表长

p=L->next;  
i=0;             
while(p)
{i++;
p=p->next;
}
int  ListLength_L(LinkList L){
//返回L中数据元素个数
    LinkList p;
    p=L->next;  //p指向第一个结点
     i=0;             
     while(p){//遍历单链表,统计结点数
           i++;
           p=p->next;    
}
return i; }

(4)判断是否为空

int ListEmpty(LinkList L)
{ 
    //若L为空表,则返回1,否则返回0 
   if(L->next)   //非空 
     return 0;
   else
     return 1;
 }

3、对链表的操作

(1)添加元素:在L中第i个元素之前插入数据元素e 

Status ListInsert_L(LinkList &L,int i,ElemType e){ 
     p=L;j=0; 
      while(p&&j<i−1){p=p->next;++j;}    //寻找第i−1个结点 
      if(!p||j>i−1)return ERROR;    //i大于表长 + 1或者小于1  
      s=new LNode;            //生成新结点s 
      s->data=e;                         //将结点s的数据域置为e 
      s->next=p->next;                     //将结点s插入L中 
      p->next=s; 
      return OK; 
}//ListInsert_L 

时间复杂度:O(1)

(2)删除元素:将线性表L中第i个数据元素删除

 Status ListDelete_L(LinkList &L,int i,ElemType &e){
    p=L;j=0; 
    while(p->next &&j<i-1){                  //寻找第i个结点,并令p指向其前驱 
        p=p->next; ++j; 
    } 
    if(!(p->next)||j>i-1) return ERROR; //删除位置不合理 
    q=p->next;                                        //临时保存被删结点的地址以备释放 
    p->next=q->next;                       //改变删除结点前驱结点的指针域 
    e=q->data;                                     //保存删除结点的数据域 
    delete q;                                     //释放删除结点的空间 
 return OK; 
}//ListDelete_L 

时间复杂度:O(1)

(3)查找:在线性表L中查找值为e的数据元素

int LocateELem_L (LinkList L,Elemtype e) {
 //返回L中值为e的数据元素的位置序号,查找失败返回0 
  p=L->next; j=1;
  while(p &&p->data!=e)  
        {p=p->next;  j++;}                  
  if(p) return j; 
  else return 0;
} 

时间复杂度:O(n)

4、单链表的创建

(1)前插法

void CreateList_F(LinkList &L,int n){ 
     L=new LNode; 
      L->next=NULL; //先建立一个带头结点的单链表 
      for(i=n;i>0;--i){ 
        p=new LNode; //生成新结点 
        cin>>p->data; //输入元素值 
        p->next=L->next;L->next=p;     //插入到表头 
     } 
}//CreateList_F 
  • 生成新结点
  • 将读入数据存放到新结点的数据域中
  • 将该新结点插入到链表的前端

(2)尾插法

void CreateList_L(LinkList &L,int n){ 
      //正位序输入n个元素的值,建立带表头结点的单链表L 
      L=new LNode; 
      L->next=NULL;     
      r=L;                                     //尾指针r指向头结点 
      for(i=0;i<n;++i){ 
        p=new LNode;                //生成新结点 
        cin>>p->data;                  //输入元素值 
        p->next=NULL; r->next=p;       //插入到表尾 
        r=p;                                       //r指向新的尾结点 
      } 
}//CreateList_L 
  • 从一个空表L开始,将新结点逐个插入到链表的尾部,尾指针r指向链表的尾结点
  • 初始时,r同L均指向头结点。每读入一个数据元素则申请一个新结点,将新结点插入到尾结点后,r指向新结点

5、线性表和链表的比较

(1)空间

  • 存储空间:顺序表需要预先分配,会导致空间闲置或溢出现象,链表动态分配,不会出现存储空间闲置或溢出现象
  • 存储密度:线性表等于1,链表小于1

(2)时间

  • 取数据:顺序表是随机存取,时间复杂度为O(1),链表是顺序存取,时间复杂度为O(n)
  • 插入删除:顺序表需要移动元素,时间复杂度为O(n),链表不需要,时间复杂度为O(1)

(3)适用范围

线性表

 表长变化不大,且能事先确定变化的范围
很少进行插入或删除操作,经常按元素位置序号访问数据元素

链表

长度变化较大
频繁进行插入或删除操作

原文地址:https://www.cnblogs.com/zhai1997/p/13370521.html