25考研数据结构复习·2.3链表.2

  • 基本操作的实现

    • 插入和删除

      插入、删除的代码都要会写,都重要

      • 插入

        • 按位序插入
          • 带头结点

            ListInsert(&L,i,e): 插入操作。在表L中的第i个位置上插入指定元素e。

头结点可以看作“第0个”结点 

  • 代码实现
    typedef struct LNode{  
    		ElemType data;  
    		struct LNode *next;  
    }LNode, *LinkList;
    
    //第i个位置上插入指定元素e(带头结点)
    bool ListInsert(LinkList &L,int i,ElemType e){
    		if(i < 1)
    				return false;
    		//优化
    		//LNode *p = GetElem(L,i -1);  //找到第i个结点
    		LNode *p;  //指针p指向当前扫描到的结点
    		int j = 0;  //当前p指向的是第几个结点
    		p = L;  //L指向头结点,头结点是第0个结点(不存数据)
    		//重要循环
    		while(p != NULL && j < i-1){
    				p = p -> next;
    				j++;
    		}
    		if(p == NULL)  //i值不合法
    				return false;
    		LNode *s = (LNode *)malloc(sizeof(LNode));
    		s -> data = e;
    		//以下两句代码顺序不可颠倒
    		s ->next = p -> next;
    		p -> next = s;  //将结点s连到p之后
    		return true;  //插入成功
    }
    
    

    !分析:

    1.如果i = 1(插在表头)

最好时间复杂度:O(1)

2.如果i = 3(插在表中) 自己走一遍代码即可看懂

3.如果i = 5(插在表尾)

最坏时间复杂度:O(n)

4.如果i = 6 (i > length)

  • 不带头结点

    ListInsert(&L,i,e): 插入操作。在表L中的第i个位置上插入指定元素e。

    • 不存在“第0个”结点,因此i = 1时需要特殊处理
  • 代码实现
    typedef struct LNode{  
    		ElemType data;  
    		struct LNode *next;  
    }LNode, *LinkList;
    
    bool ListInsert(LinkList &L,int i,ElemType e){
    		if(i < 1)
    				return false;
    		if(i == 1){  //插入第1个结点的操作与其他结点操作不同
    				LNode *s = (LNode *)malloc(sizeof(LNode));
    				s -> data = e;
    				s -> next = L;
    				L = s;  //头指针指向新结点
    				return true;
    		}
    		LNode *p;  //指针p指向当前扫描到的结点
    		int j = 1;  //当前p指向的是第几个结点
    		p = L;  //p指向第1个结点(注意:不是头结点)
    		//重要循环
    		while(p != NULL && j < i-1){  //循环找到第i - 1 个结点
    				p = p -> next;
    				j++;
    		}
    		if(p == NULL)  //i值不合法
    				return false;
    		LNode *s = (LNode *)malloc(sizeof(LNode));
    		s -> data = e;
    		//以下两句代码顺序不可颠倒
    		s ->next = p -> next;
    		p -> next = s;  //将结点s连到p之后
    		return true;  //插入成功
    }
    

    !分析:

    1.如果i = 1(插在表头)

2.如果i > 1…  后续逻辑和带头结点的一样

💡 考试中带头、不带头都有可能考察,注意审题

  • 指定结点的后插操作
    typedef struct LNode{  
    		ElemType data;  
    		struct LNode *next;  
    }LNode, *LinkList;
    
    //后插操作:在p结点之后插入元素e
    bool InsertNextNode(LNode *p,ElemType e){
    		if(p == NULL)  
    				return false;  //考虑到非法情况:健壮性
    		LNode *s = (LNode *)malloc(sizeof(LNode));
    		// 某些情况下有可能分配失败(如内存不足)
    		if(s == NULL)  //内存分配失败
    				return false;
    		s -> data =e;  //用结点s保存数据元素e
    		s ->next = p -> next;
    		p -> next = s;  //将结点s连到p之后
    		return true;  
    }
    

    时间复杂度:O(1)

    • 带头结点代码优化
      typedef struct LNode{  
      		ElemType data;  
      		struct LNode *next;  
      }LNode, *LinkList;
      
      //第i个位置上插入指定元素e(带头结点)
      bool ListInsert(LinkList &L,int i,ElemType e){
      		if(i < 1)
      				return false;
      		LNode *p;  //指针p指向当前扫描到的结点
      		int j = 0;  //当前p指向的是第几个结点
      		p = L;  //L指向头结点,头结点是第0个结点(不存数据)
      		//重要循环
      		while(p != NULL && j < i-1){
      				p = p -> next;
      				j++;
      		}
      		//优化
      		return InsertNextNode(p,e);
      }
      

      封装(基本操作)的好处

      避免重复代码,简介,易维护

  • 指定结点的前插操作
    • 方法1
      //前插操作:在p结点之前插入元素e
      //传入头指针
      bool InsertPriorNode(LinkList L, LNode *p, ElemType e)
      

      时间复杂度:O(n)

  • 方法2
    //前插操作:在p结点之前插入元素e
    bool InsertPriorNode(LNode *p, ElemType e){
    		if(p == NULL)  
    				return false;
    		LNode *s = (LNode *)malloc(sizeof(LNode));
    		if(s == NULL)  //内存分配失败
    				return false;
    		s -> next = p -> next;
    		p -> next = s;  //新结点s连到p之后
    		s -> data = p -> data;  //将p中元素复制到s中
    		p -> data = e;  //p中元素覆盖为e
    		return true;  
    }
    

    时间复杂度:O(1)

  • 删除

    • 按位序删除(带头结点)

      ListDelet(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值

(找到第i -1 个结点,将其指针指向第i + 1个结点,并释放第i个结点 )

  • 代码实现
    typedef struct LNode{  
    		ElemType data;  
    		struct LNode *next;  
    }LNode, *LinkList;
    
    bool ListDelete(LinkList &L,int i,ElemType &e){
    		if(i < 1)
    				return false;
    		//优化
    		//LNode *p = GetElem(L,i -1);  //找到第i个结点
    		LNode *p;  //指针p指向当前扫描到的结点
    		int j = 0;  //当前p指向的是第几个结点
    		p = L;  //L指向头结点,头结点是第0个结点(不存数据)
    		//重要循环
    		while(p != NULL && j < i-1){  //循环找到第i - 1个结点
    				p = p -> next;
    				j++;
    		}
    		if(p == NULL)  //i值不合法
    				return false;
    		if(p -> next == NULL)  //第i - 1 个结点之后已无其他结点
    				return false;
    		LNode *q = p -> next;   //令q指向被删除结点
    		e = q -> data;  //用e返回元素的值
    		p -> next = q -> next;  //将*q结点从链中“断开”
    		free(q);  //释放结点的存储空间
    		return true;  //插入成功
    }
    

    ! 分析:

    1.如果i = 4…

最坏、平均时间复杂度:O(n)

最好时间复杂度: O(1)


 ❔如果不带头结点,删除第1个元素,是否需要特殊处理?

❕需要特殊处理,除第一个元素的其他元素要删除需要找到该元素的前一个元素,保证链不断 而第一个元素只要修改L的指向

  • 指定结点的删除
    //删除指定结点p
    bool DeleteNode(LNode *p)
    
    • 删除结点p,需要修改其前驱结点的next指针
  • 方法1:传入头指针,循环寻找p的前驱结点

  • 方法2:偷天换日(类似于结点前插的实现)

    //删除指定结点p
    bool DeleteNode(LNode *p){
    		if(p == NULL)
    				return false;
    		LNode *q = p -> next;  //令q指向*p的后继结点
    		p -> data = p -> next -> data;  //和后继节点交换数据域
    		p -> next = q -> next;  //将*q结点从链中“断开” 可能是指向一个结点,也可能是指向NULL
    		free(q);  //释放后继结点的内存空间
    		return true;
    }
    

    时间复杂度:O(1)


    ❔ 如果p是最后一个结点

    ❕ 只能从表头开始依次寻找p的前驱,时间复杂度O(n)


    查找

    • 按位查找

      只探讨“带头结点”的情况

      GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值。

    • 代码实现

//按位查找,返回第i个元素(带头结点)
LNode * GetElem(LinkList L,int i){
		if(i < 0)
				return NULL;
		LNode *p;  //指针p指向当前扫描到的结点
		int j = 0;  //当前p指向的是第几个结点
		p = L;  //L指向头结点,头结点是第0个结点(不存数据)
		while(p != NULL && j < i){  //循环找到第i个结点
				p = p -> next;
				j++;
		}
		return p;
}

边界情况:1.如果i = 0  2.如果i = 8

普通情况:如果i = 3

平均时间复杂度:O(n)

  • 按值查找

    LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。

    • 代码实现
      //按值查找,找到数据域==e 的特点
      //注释:假设本例中ElemType是int
      LNode * LocateElem(LinkList L,ElemType e){
      		LNode *p = L -> next;
      		//从第1个结点开始查找数据域为e的结点
      		while (p != NULL && p -> data != e)
      				p = p -> next;
      		return p;  //找到后返回该结点指针,否则返回NULL
      } 
      

      (头指针 → 5 → 8 → 10 → 2 → NULL)

    • 能找到的情况:如果e = 8

    • 不能找到的情况:如果e = 6

      平均时间复杂度:O(n)

  • 求表的长度

    //求表的长度
    int Length(LinkList L){
    		int len = 0;  //统计表长
    		LNode *p = L;
    		while(p -> next != NULL){
    				p = p -> next;
    				len++;
    		}
    		return len;
    }
    

    时间复杂度:O(n)

总结: 

 

  • 建立

    ❔ 如果给你很多个数据元素(ElemType),要把他们存放到一个单链表里,怎么做?

    ❕step1:初始化一个单链表。 step2:每次取一个数据元素,插入到表尾/表头

    注:探讨带头结点的情况👇

  • 尾插法

    typedef struct LNode{  //定义单链表结点类型
    		ElemType data;  //每个结点存放一个数据元素
    		struct LNode *next;  //指针指向下一个结点
    }LNode, *LinkList;
    
    //初始化一个单链表(带头结点)
    bool InitList(LinkList &L){
    		L = (LNode *) malloc(sizeif(LNode));  //分配一个头节点
    		if (L == NULL)  //内存不足,分配失败
    				return false;
    		L -> next = NULL;  //头节点之后暂时还没有结点
    		return true;
    }
    
    void test(){
    		LinkList L; //声明一个指向单链表的指针
    		//初始化一个空表
    		InitList(L);
    		//...后续代码...
    }
    
    //第i个位置上插入指定元素e(带头结点)
    bool ListInsert(LinkList &L,int i,ElemType e){
    		if(i < 1)
    				return false;
    		LNode *p;  //指针p指向当前扫描到的结点
    		int j = 0;  //当前p指向的是第几个结点
    		p = L;  //L指向头结点,头结点是第0个结点(不存数据)
    		//每次都从头开始之后遍历,时间复杂度为O(n²)
    		**while(p != NULL && j < i-1){  //循环找到第i - 1个结点
    				p = p -> next;
    				j++;
    		}**
    		if(p == NULL)  //i值不合法
    				return false;
    		LNode *s = (LNode *)malloc(sizeof(LNode));
    		s -> data = e;
    		s ->next = p -> next;
    		p -> next = s;  //将结点s连到p之后
    		return true;  //插入成功
    }
    
    • 初始化单链表

    • 设置变量length记录链表长度

    • While循环{

      每次取一个数据元素e;

      ListInsert(L,length + 1,e)插到尾部;

      length++;  }


    因为while循环时间复杂度O(n²)太高,故可以建立一个尾指针👇

    LinkList List_TailInsert(LinkList &L){  //正向建立单链表
    		int x;  //设ElemType为整型
    		L = (LinkList)malloc(sizeof(LNode));  //建立头结点
    		LNode *s,*r = L;  //r为表尾指针
    		scanf("%d",&x);  //输入结点的值
    		while(x != 9999){  //输入9999表示结束
    				**s = (LNode *)malloc(sizeof(LNode));
    				s -> data = x;
    				r -> next = s;**
    				r = s;  //r指向新的表尾结点 永远保持r指向最后一个结点
    				scanf("%d",&x);
    		}
    		r -> next = NULL;  //尾结点指针置空
    		return L;
    }
    

    时间复杂度:O(n)

💡 注意设置一个指向表尾结点的指针

  • 头插法

    💡 核心:对头结点的后插操作

    • 初始化单链表

    • while 循环{

      每次取一个数据元素e;

      InsertNextNode(L,e);

      }

    LinkList List_HeadInsert(LinkList &L){  //逆向建立单链表
    		LNode *s;
    		int x;  
    		L = (LinkList)malloc(sizeof(LNode));  //建立头结点
    		//只要是初始化但列联表,都先把头指针指向NULL (防止指向脏数据)
    		**L -> next = NULL;  //初始为空链表**
    		scanf("%d",&x);  //输入结点的值
    		while(x != 9999){  //输入9999表示结束
    				s = (LNode *)malloc(sizeof(LNode));  //创建新结点
    				s -> data = x;
    				r -> next = L -> next;
    				L -> next = s;  //将新结点插入表中,L为头指针
    				scanf("%d",&x);
    		}
    		return L;
    }
    

    ❗ 重要应用——链表的逆置

  • 💡 核心就是初始化操作指定结点的后插操作

  • 17
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Annabelle.02

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值