单链表

单链表

  • 每个结点除了存档元素外,还要存储下一个结点的指针
  • 优点:不要求大片连续空间,改变容量方便
  • 缺点:不可随机存取

用代码定义一个单链表

struct LNode{			 //定义单链表结点类型
    ElemType data;		 //每个结点存放一个数据元素(数据域)
    struct LNode *next;  //指针指向下一个结点
};

增加一个新结点:在内存中申请一个结点需要空间,并用指针p指向这个结点

struct LNode *p = (struct LNode *)malloc(sizeof(struct LNode));
  • typedef关键字——数据类型重命名

    typedef struct LNode LNode;//先对struct LNode重命名为LNode,简便后续操作
    LNode *p = (LNode *)malloc(sizeof(LNode));
    
// 书本上更简洁的命名方法
typedef struct LNode{//定义单链表结点类型
	ElemType data;
	struct LNode *next;
}Node,*LinkList;//这两个都是struct LNode的重命名

上面的代码等价于

struct LNode{
  ElemType data;
  struct LNode *next;
};

typedef struct LNode LNode;
typedef struct LNpde *LinkList;

这样,如果要表示一个单链表时,只需要声明一个头指针L,指向单链表的第一个结点

LNode* L;
LinkList L;//或者这样都可以声明一个指向单链表第一个结点的头指针,这种当时代码可读性更强
  • 具体有什么区别,看下面的代码

    typedef struct LNode{
    	ElemType data;
        struct LNode *next;
    }LNode, *LinkList;
    
    LNode * GetElem(LinkList L, int i){//这里两种声明方式都用到了,其实LinkList L也完全可以用LNode *L来替换,但是为什么要这么写呢?原因是:——当我们使用LinkList时,我们强调这是一个单链表;——当我们使用LNode *时,我们强调这是一个结点
    	int j = 1;
        LNode * p = L->next;
        if(i == 0)
            return L;
        if(i < 1)
            return NULL;
        while(p != NULL && j < i){
     		p = p->next;
            j++;
        }
        return 0;
    }
    

初始化单链表

  • 不带头结点的单链表
typedef struct LNode{//定义单链表结点类型
    ElemType data;//每个结点存放一个数据元素
    struct LNode *next;//指针指向下一个结点
}LNode, *LinkList;

//初始化一个空的单链表
bool InitList(LinkList &L){//如果这里不用&引用符号,那么是改变不了主函数中的L的,即不能把主函数中的L初始化,改变的是主函数L的另为一个副本
	L = NULL;//空表,暂时还没有任何结点(防止脏数据)
    return true;
}

//判断单链表是否为空
bool Empty(LinkList L){
    if(L == NULL)
        return true;
    else
        return false;
}
//或者,因为L==NULL判断之后返回的就是true或者false所以,下面的写法更简洁
bool Empty(LinkList L){
	return(L==NULL);
}

void test(){
    LinkList L;//声明一个指向单链表的指针
    //初始化一个空表
    InitList(L);
    //....后续代码...
}
  • 带头结点的单链表
typedef struct LNode{	ElemType data;	struct LNode *next;}LNode, *LinkList;//初始化一个单链表(带头结点)bool InitList(LinkList &L){	L = (LNode *)malloc(sizeof(LNode));//分配一个头结点    if(L==NULL)//内存不足,分配失败        return false;    L->next = NULL;//头结点之后暂时还没有结点    return true;}//判断单链表是否为空(带头结点)bool Empty(LinkList L){	if(L->next == NULL)        return true;    else        return false;}void test(){	LinkList L;//声明一个指向单链表的指针    InitList(L);//初始化一个空表    //....后续代码...}
image-20210722143056803

带头结点的指针写代码会更加方便

单链表的插入删除

插入

  • 按位序插入:带头结点的插入ListInsert(&L, i,e):在表L中的第i个位置上插入指定元素e

    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){//循环找到第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;//插入成功}
    
  • 按位序插入:不带头结点的插入:找到第i-1个结点,把新结点插在其后面。不存在“第0个”结点,因此当i=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;    if(i == 1){//插入第1个结点的操作与其他结点操作不同		LNode *s = (LNode *)malloc(sizeof(LNode));        s->data = e;        s->next = L;        L = s;//头指针指向新节点    }        LNode *p;//指针p指向当前扫描到的结点    int j = 1;//当前p指向的是第几个结点    p = L;//L指向头结点,头结点是第0个结点(不存数据)    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;//插入成功}
    
  • 后插操作:在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->next = p->next;    p->next = s;    return true;}
    
  • 前插操作:在p结点之前插入元素e

    bool InsertPriorNode(LNode *p, LNode *s){	if(p==NULL || s = NULL)        return false;    s->next = p->next;    p->next = s;    ElemType temp = p->data;    p->data = s->data;    s->data = temp;    return false;}
    

删除

  • ListDelete(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值
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){//循环找到第i-1个结点		p = p->next;        j++;    }    if(p==NULL)//i值不合法        return false;    LNode *q = p->next;    e = p->data;    p->next = q->next;    free(q);    return true;
  • 删除指定结点

    如果p结点是最后一个结点的话,p->next->data = NULL,就会出现空指针的错误,如果出现这种情况,那我们就只能引入头指针,来从头开始一个个搞,

    bool DeleteNode(LNode *p){    if(p==NULL)        return false;    LNode *q = p->next;    p->data = p->next->data;    p->next = q->next;    free(q);    return true;    }
    

单链表的查找

  • 按位查找: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;}
    
  • 按值查找:LocateElem(L,e)。在表L中查找具有给定个关键字值的元素

    //按值查找,找到数据域==e的结点LNode *LocateElem(LinkList L,ElemType e){	LNode *p = L->next;//从第一个结点开始查找数据域为e的结点	while(p!=NULL && p->data !=e)		p = p->next;	return p;//找到后返回该结点指针,否则返回NULL}
    

求表的长度

int Length(LinkList L){
	int len = 0;
	LNode *p = L;
	while(p->next != NULL){
		p = p->next;
		len++;
	}
	return len;
}

单链表的建立

step1.初始化一个单链表

step2.每次取一个数据元素,插入到表尾/表头

尾插法建立单链表

//定义单链表
typedef struct LNode{
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;
//对指定结点的后插操作
LinkList  List_Taillnsert(LinkList &L){
    int x;
    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->next = NULL;//尾结点指针置空
    return L;
}

头插法建立单链表

  • 头插法的重要应用,链表的逆置。原理是头插法读入数据的顺序与生成链表中元素的顺序是相反的。
  • 所以我们可以在指针扫描各个结点的时候,取出各个数据,在用头插法插入到头结点的后面,这样就可以实现链表逆置了
LinkList List_HeadInsert(LinkList &L){
    LNode *s;
    int x;
    L = (LinkList)malloc(sizeof(LNode));
    L->next = NULL;
    scanf("%d",&x);
    while(x!=9999){
        s = (LNode *)malloc(sizeof(LNode));
        s->data = x;
        s->next = L->next;
        L->next = s;
     	scanf("%d", &x);
    }
    return L;   
}
posted @ 2021-07-22 16:38  shiff  阅读(225)  评论(0编辑  收藏  举报