小肥柴慢慢手写数据结构(C篇)(2-1 单链表 SingleLinkedList self版实现(1))

小肥柴慢慢学习数据结构笔记(C篇)(2-1 单链表SingleLinkedList self版本实现(1))

目录

2-1 为啥要有链表

  1. 在1篇中“动态”数组ArrayList的学习中,我们发现这个动态是依靠resize操作实现的,非真正意义上的动态,因为我们需要随时关注size和capacity之间的关系。
  2. 虽然ArrayList的查询效率非常高,但是插入和删除开销大。

针对以上弱点,给出一个新的数据结构–链表。

2-2 出列ADT

  1. 节点struct
    老样子,不会编码先写中文,然后翻译
    struct 节点{
    (1)存放的数据
    (2)下一个节点的位置(地址)
    }
    可能需要一个虚拟头结点(DummyHead),方便编程。
struct Node{
	ElementType Element; //数据
	Position Next;       //下一个节点地址
};
typedef struct Node *PrtToNode;  //仿照老外的写法,其实也可以直接用*
typedef PrtToNode List;  //链表虚拟头结点,代表了一个链表
typedef PrtToNode Position; //链表上某个位置的节点

有关Head和Tail的问题,其实本质就是让每一个节点都有前驱(prev)节点和后驱(next不为空)节点,让各种功能实现更加方便,这一节我们仅使用一个Head,你会发现在尾插操作时,有Tail是一件非常幸福的事情。—所有的设计都是为了更好的实现功能,尽量的避免bug。
在这里插入图片描述

  1. 操作
List createList();    //新生成一个链表                 
int IsEmpty(List L);        //链表判空
Position Find(ElementType X, List L); //找到指定元素的第一个节点
Position FindPrevious(ElementType X, List L); //找到指定元素的第一个节点的前驱节点,删除有用
void Insert(ElementType X, List L, Position P); //在节点P后面插入新的元素X
void InsertFirst(ElementType X, List L); //头插
void InsertLast(ElementType X, List L); //尾插
void Delete(ElementType X, List L); //删除指定元素第一个节点
void DeleteList(List L); //清空链表
Position Advance(Position P); //获得Next节点
Position Header(List L); //获得虚拟头结点
Position First(List L); //获得真正存有数据的第一个节点
Position Last(List L); //获得最后一个存有数据的节点
ElementType Retrieve(Position P); //获取当前节点数据
void PrintList(List L); //打印节点

注: 有的朋友在Node的基础上再封装(Head + Tail)或者(prev+next),都是设计优化,本篇先实现一个简单版本,对不熟悉的同学来讲,代码是可以一点点慢慢优化的。

2-3 边想边写

其实链表的操作就是对节点指向的操作,关键点在于
(1)要保存下当前被操作的节点对象地址
(2)改变指向关系
(3)视情况释放暂存节点,或者更改游标位置(first/last,中间量tmp)
找到规律,自己在纸上写明白,画明白了,并没有那么玄乎。

  1. 插入
    在这里插入图片描述
void Insert(ElementType X, List L, Position P){
	Position TmpCell = (Position)malloc(sizeof(struct Node));
	if(L == NULL) //空指针防不胜防
		printf("\ninsert list failed, out of memery!\n");
		
	TmpCell->Element = X;
	TmpCell->Next = P->Next;
	P->Next = TmpCell;
}
  1. 删除
    在这里插入图片描述
    关键在于找到prev节点
Position FindPrevious(ElementType X, List L){
   Position P = L; //从虚拟头结点开始遍历,避开各种判断
   while(P->Next != NULL && P->Next->Element != X)
   	P = P->Next;
   return P;
}
void Delete(ElementType X, List L){
   Position Prev, TmpCell;
   Prev = FindPrevious(X, L);
   if(Prev != NULL){
   	TmpCell = Prev->Next;
   	Prev->Next = Prev->Next->Next;
   	free(TmpCell);
   }
}
  1. 遍历
    遍历也是要根据具体要求来的,可以从DummyHead开始,也可以从First开始,重点是怎么好用怎么来。
    Position P = L->Next; //从L开始也行
	while(P!=NULL){
       //...
   	   P = P->Next;
    }
  1. 现阶段所有代码
    (1)SingleLinkedList.h
typedef int ElementType;

#ifndef _List_H
#define _List_H

struct Node;
typedef struct Node *PrtToNode;
typedef PrtToNode List;
typedef PrtToNode Position;

List createList();                     
int IsEmpty(List L);                  
Position Find(ElementType X, List L);
Position FindPrevious(ElementType X, List L);
void Insert(ElementType X, List L, Position P);
void InsertFirst(ElementType X, List L);
void InsertLast(ElementType X, List L);
void Delete(ElementType X, List L);
void DeleteList(List L);
Position Advance(Position P);
Position Header(List L);
Position First(List L);
Position Last(List L);
ElementType Retrieve(Position P);
void PrintList(List L);
#endif

(2)SingleLinkedList.c

#include <stdlib.h>
#include <stdio.h>
#include "SingleLinkedList.h"

struct Node{
   ElementType Element;
   Position Next;
};

List createList(){
   Position L = (Position)malloc(sizeof(struct Node));
   if(L == NULL){
   	printf("\ncreate list failed, out of memery!\n");
   	return NULL;
   }
   
   L->Next = NULL;
   return L;
}                  

int IsEmpty(List L){
   return L->Next == NULL;
}                

Position Find(ElementType X, List L){
   Position P = L->Next;
   while(P!= NULL && P->Element != X)
   	P = P->Next;
   return P;
}

Position FindPrevious(ElementType X, List L){
   Position P = L;
   while(P->Next != NULL && P->Next->Element != X)
   	P = P->Next;
   return P;
}

void Insert(ElementType X, List L, Position P){
   Position TmpCell = (Position)malloc(sizeof(struct Node));
   if(L == NULL)
   	printf("\ninsert list failed, out of memery!\n");
   	
   TmpCell->Element = X;
   TmpCell->Next = P->Next;
   P->Next = TmpCell;
}

void InsertFirst(ElementType X, List L){
   Position TmpCell = (Position)malloc(sizeof(struct Node));
   if(L == NULL)
   	printf("\ninsert list first failed, out of memery!\n");
   	
   TmpCell->Element = X;
   TmpCell->Next = L->Next;
   L->Next = TmpCell;
}

void InsertLast(ElementType X, List L){
   Position last = Last(L);
   if(last != NULL)
   	Insert(X, L, last);
   else{
   	Position P = (Position)malloc(sizeof(struct Node));
   	P->Element = X;
   	P->Next = NULL;
   	L->Next = P;
   }
   	
}

void Delete(ElementType X, List L){
   Position Prev, TmpCell;
   Prev = FindPrevious(X, L);
   if(Prev != NULL){
   	TmpCell = Prev->Next;
   	Prev->Next = Prev->Next->Next;
   	free(TmpCell);
   }
}

void DeleteList(List L){
   Position P, TmpCell;
   P = L->Next;
   L->Next = NULL;
   while(P != NULL){
   	TmpCell = P->Next;
   	free(P);
   	P = TmpCell;
   }
}

Position Advance(Position P){
   return P==NULL ? NULL : P->Next;
}

Position Header(List L){
   return L;
}

Position First(List L){
   return L == NULL ? NULL : L->Next;
}

Position Last(List L){
   if(IsEmpty(L))
   	return NULL;

   Position P = L->Next;
   while(P!=NULL && P->Next != NULL)
   	P = P->Next; 
   return P;
}

ElementType Retrieve(Position P){
   return P==NULL ? NULL : P->Element;
}

void PrintList(List L){
   Position P = L->Next;
   printf("\nDummyHead->");
   while(P!=NULL){
   	printf("[%d]->", P->Element);
   	P = P->Next;
   }
   printf("NULL\n");
}

(3)Main.c 测试

#include <stdio.h>
#include <stdlib.h>
#include "SingleLinkedList.h"

int main(int argc, char *argv[]) {
   Position last;
   printf("\n===============test create && insert :===================\n");
   int i;
   List list = createList();
   Position P = list;
   for(i = 0; i < 10; i++){
   	Insert(i, list, P);
   	P = Advance(P);
   }
   PrintList(list);

   printf("\n===============test insert first :===================\n");
   InsertFirst(-1, list);
   PrintList(list);

   printf("\n===============test delete :=========================\n");
   for(i = 0; i < 10; i+= 2)
       Delete(i, list);
   PrintList(list);

   printf("\n===============test find :============================\n");
   for(i = 0; i < 10; i++)
       if((i % 2 == 0 ) == (Find(i, list) != NULL ) )
           printf( "Find fails\n" );
           
   printf("\n===============test last :============================\n");
   last = Last(list);
   printf("\nlast is %d\n", Retrieve(last));
   
   printf("\n===============test insert last :============================\n");
   InsertLast(10, list);
   PrintList(list);

   printf("\n===============test delete list :=====================\n");
   DeleteList(list);     
   if(list == NULL)
   	printf("L is null\n");
   PrintList(list);
   
   printf("\n===============test last again:========================\n");
   last = Last(list);
   printf("\nag last is %d\n", Retrieve(last));
   
   printf("\n===============test insert last again:=================\n");
   InsertLast(100, list);
   PrintList(list);
   
   return 0;
}

2-4 反思一下

  1. 检索操作很耗时,在末尾插入也是,考虑升级实现方法,添加一个尾结点。
  2. 链表的翻转这个经典的操作还需实现

PS:发现很多人直接盗用刘大佬的PPT资料和代码,却不给注明转载或参考,呵呵。

下一步我们将继续改进self版本链表实现。

后记:可能有人会吐槽为何代码写的如此繁琐,其实吧我是参考
《数据结构与算法分析:C语言描述》(原书第2版)中的模式去写的,对于初学者来讲是很容易理解的,过一段时间,我会给出面试用的简写版本,方便记(背)忆(书)。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值