数据结构和算法

线性表的链式表示和实现

线性表的链式存储结构

  • 线性表中数据元素(结点)在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理位置不一定相邻
  • 结点(数据+指针)
  • 链表(n个结点由指针链组成一个链表)
  1. 单链表:每个结点只有指针域
  2. 双链表:每个结点有两个指针域
  3. 循环链表:链表节点首尾相接

单链表的定义和表示

  1. 带头结点的单链表(非空表、空表)
  • 单链表是由表头唯一确定,由此单链表可以用头指针的名字来命名,若头指针名为L,则把链表称为表L
  • 单链表的存储结构(数据域+指针域)
typedef struct Lnoce{ //声明结点的类型和指向结点的指针类型
    ElemType data;  //结点的数据域
    struct Lnode *next; //结点的指域
}Lnode,*LinkList; //LinkList为指向结构体Lnode的指针类型
  • 定义链表L : LinkList L;
  • 定义结点指针p: LNode*p <=> LinkList p;

image.png

 

重点:单链表基本操作的实现

单链表的初始化(带头结点的单链表)

  • 即构造一个如图的空表

算法步骤

  • 生成新结点作头节点,用头指针L指向头节点
  • 将头节点的指针域置空
  • 算法描述:
Status InitList_L(LinkList &L){
   L = new LNode;//或L = (LinkList)malloc(sizeof(LNode));
   L->next = NULL;
   return OK;
}

补充单链表的几个常用简单算法

补充算法1---判断链表是否为空:

空表:链表中无元素,称为空链表(头指针和头结点仍然在)
【算法思路】判断头节点指针域是否为空

int ListEmpty(ListList L){ //若L为空表,则返回1,否则返回0
   if(L->next) return 0;//非空
   else  return 1;
}
补充算法2---单链表的销毁:链表销毁后不存在

【算法思路】从头指针开始,依次释放所有结点

image.png

 

Status Destroylist_L(LinkList &L){  //销毁单链表L
   Lnode *p;//或LinkList p;
   while(L){
      p=L;
      L=L-next;
      delete p;
   }
   return OK;
}
补充算法3---清空链表:

链表仍存在,但链表中无元素,成为空链表(头指针和头节点仍然在)
【算法思路】依次释放所有结点,并将头节点指针域设置为空

image.png

Status ClearList(LinkList &L){ //将L重置为空表
   Lnode *p,*q; //或LinkList p,q;
   p=L->next;
  while(p){  //没到表尾
     q=p->next;
    delete p;
     p=q;
  }
  L->next = NULL; //头结点指针域为空
  return OK;
}
 
补充算法4---求单链表的表长

【算法思路】从首元结点开始,依次计数所有结点

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

【算法思路】从链表的头指针出发,顺着链域next逐个结点往下搜索,直至搜索到第i个结点为止。因此,链表不是随机存储结构
【算法步骤】

  1. 从第1个结点(L->next)顺链扫描,用指针p指向当前扫描到的结点,p初值p=L->next.
  2. j做计数器,累计当前扫描过的结点数,j初值为1.
  3. 当p指向扫描到的下一结点时,计数器j加1.
  4. 当j==i时,p所指的结点就是要找的第i个结点
Status GetElem_L(LinkList L,int i,Elem Type &e){
   p =L->next;
   j=i;  //初始化
  while(p&&j<i){  //向后扫描,直到p指向第i个元素或p为空
     p=p->next;++j;
  }
  if(!p || j>i) return ERROR; //第i个元素不存在
  e =p->data;  //取第i个元素
  return OK;
}//GeteElem_L
补充算法6---按值查找(根据指定数据获取该数据所在的位置<地址>)

【算法步骤】

  1. 从第一个结点起,依次和e相比较
  2. 如果找到一个其值与e相等的数据元素,则返回其在链表中的“位置”或地址;
  3. 如果查找整个链表都没有找到其值和e相等的元素,则返回0或“NULL”.

【算法描述】

Lnode *LocateElem_L(LinkList L,Elemtype e){
   //在线性表L中查找值为e的数据元素
   //找到,则返回L中值为e的数据元素的地址,查找失败返回NULL
  p=L->next;
  while(p&&p->data!=e){
         p = p->next;
   }
   return p;
}
补充算法7---按值查找(根据指定数据获取该数据位置序号)

【算法描述】

//在线性表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;
}
补充算法8---插入(在第i个结点前插入值为e的新结点)

【算法步骤】

  1. 首先找到image.png的存储位置p
  2. 生成一个数据域为e的新结点s.
  3. 插入新结点:
    • 新结点的指针域指向结点image.png
    • 结点image.png的指针域指向新结点

【算法描述】

Status ListInsert_L(LinkList &L,int i,ElemType e){
  p=L;j=0;
  while(p&&j<i-1){ //寻找第i-1个结点,p指向i-1结点
    p=p->next;++j;
  }
  if(!p || j>i-1) return ERROR; //i大于表长+1或者小于1,插入位置非法
  s = new LNode; s->data = e; //生成新结点s,将结点s的数据域置为e
  s->next = p->next; //将结点s插入关键步骤
  p->next = s;//将结点s插入关键步骤
  return OK;
} //ListInsert_L
 
补充算法9---删除(删除第i个结点)

【算法步骤】

  1. 首先找到image.png的存储位置p,保存要删除的image.png的值
  2. 令p->next 指向image.png
  3. 关键步骤
p->next = p->next->next;
  1. 释放结点image.png的空间
    【算法描述】
    //将线性表L中第i个数据元素删除
    Status ListDelete_L(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; //删除位置不合理
    q = p->next; //临时保存被删结点的地址以备释放
    p->next = q->next;//改变删除结点前驱结点的指针域
    e = q->data; //保存删除结点的数据域
    delete q; //释放删除结点的空间
    return OK;
    }//ListDelete_L
单链表的查找、插入、删除算法事件效率分析
  1. 查找
    因线性链表只能顺序存取,即在查找时要从头指针找起,查找的时间复杂度为O(n).
  2. 插入和删除
    因线性链表不需要移动元素,只要修改指针,一般情况下时间复杂度为O(1).
    但是,如果要在单链表中进行前插或删除操作,由于要从头查找前驱结点,所耗时间复杂度为O(n).
补充算法10---建立单链表:头插法(元素插入在链表头部,也叫前插法)
  1. 从一个空表开始,重复读入数据;
  2. 生成新结点,将读入数据存放到新结点的数据域中
  3. 从最后一个结点开始,依次将各结点插入到链表的前端
    【算法步骤】首先建造一个空结点
L = new LNode;//或C语言中如下: L=(LinkList)malloc(sizeof(LNode));
void CreateList_H(LinkList &L,int n){
    L = new LNode;
    L->next = NULL;//先建立一个带头结点的单链表
   for(i = n;i>0;i--){
     p = new LNode;//生成新节点 p =(LNode*)malloc(sizeof(LNode));
     cin>>p->data;//输入元素值scand(&p->data);
     p->next = L->next; //插入到表头
     L->next = p;
  }
}//CreateList_H;   算法的时间复杂度是O(n)
补充算法11单链表的建立--后插法(尾插法)
  1. 从一个空表L开始,将新节点逐个插入到链表的尾部,尾指针r指向链表的尾结点。
  2. 初始时,r同L均指向头节点。每读入一个数据元素则申请一个新结点,将新结点插入到尾结点后,r指向新结点
  3. 关键步骤
p->data = a;
p->next = NULL;
r->next = p;
r=p;

【算法描述】

//正位序输入n个元素的值,建立带表头结点的单链表L
void CreateList_R(LinkList &L,int n){
  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_R 时间复杂度是:O(n)
 

 
posted @   jerry-autumn  阅读(128)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程
点击右上角即可分享
微信分享提示