【数据结构】单链表的基本操作(节点建立、插入删除)

  • 单链表的基本操作
    • 链表的定义
    • 链表的创建(初始化)
      • 不带头结点的链表
      • 带头结点的链表
    • 链表的插入和删除
      • 按位序插入
        • 带头结点
        • 不带头结点
      • 指定节点的后插操作
      • 指定元素的前插操作
      • 按位序删除
        • 带头结点
        • 不带头结点
      • 指定元素的删除操作

1. 单链表的基本操作

链表是一种基本的数据结构,在很多数据结构中都会使用链表的方式进行建立,所以将链表学会学通透很有必要。

1.1. 链表的定义

链表的定义是基于结构体之上的,一个链表一般需要定义两方面的东西,一个是链表的节点,一个是链表的指针。一个链表的定义示例如下:

typedef struct ListNode {
    int data;
    struct ListNode *next;
}LNode,*List;

上述代码中的val是数据域,用来存放数据,next是指针域,用来存放下一个节点的地址。
并且该代码采用了typedef定义了一个别名LNode,用来指代链表的节点,List是链表的指针,用来指向链表的第一个节点。

1.2. 链表的创建(初始化)

链表的创建有两种方式,一种是带头结点的,另一种是不带头节点的。

1.2.1. 不带头结点的链表

不带头结点的链表的创建方式如下:

void InitList(List *L)
{
    L=NULL;
}

即使得链表的下一个节点为空。

1.2.2. 带头结点的链表

带头结点的链表的创建方式如下:

void InitList(List *L)
{
    L=(List)malloc(sizeof(LNode));
    if(L==NULL)
    {
        return ;//此时表示创建头结点失败
    }
    L->next=NULL;//头结点指向的下一个节点为空
}

头结点的好处就在于,当我们需要对链表进行一个操作的时候,不需要专门对链表的第一个节点进行特殊操作,我们只需要对所有的代码编写一个固定的函数,通过传递不同的值就可以实现特定高度功能。

1.3. 链表的插入和删除

1.3.1. 按位序插入

1.3.1.1. 带头结点

链表的插入,需要先找到要插入位置的前后节点,然后创建一个指针,使得前一个节点指向这个指针,这个指针再指向下一个节点。

在按位序插入的过程中,我们需要输入链表的头结点(供遍历寻找目标位置),要插入的位置和元素。
示例代码如下:

bool ListInsert(List *L,int i,int e)
{
    if(i<1)
    {
        return false;
    }
    LNode *p; //p表示当前指向的是哪个节点
    int j=0; //j表示的是当前指向节点的位置
    p=L; //目前先让p指向头节点
    while(p!=NULL&&j<i-1) //循环找到要插入节点的前一个节点
    {
        p=p->next;
        j++;
    }
    LNode *s=(LNode *)malloc(sizeof(LNode));
    s.data=e;
    s->next=p->next;
    p->next=s;
    return true;
}
1.3.1.2. 不带头结点

不带头结点的插入与带头结点的插入类似,但是相对带头节点来说更加繁琐。

bool ListInsert(List *L,int i,int e)
{
    if(i<1)
    {
        return false;
    }
    if(i==1)
    {
        LNode *q=(LNode *)malloc(sizeof(LNode));
        q->data=e;
        q->next=L;
        L=q;
    }
    LNode *p; //p表示当前指向的是哪个节点
    int j=0; //j表示的是当前指向节点的位置
    p=L; //目前先让p指向头节点
    while(p!=NULL&&j<i-1) //循环找到要插入节点的前一个节点
    {
        p=p->next;
        j++;
    }
    LNode *s=(LNode *)malloc(sizeof(LNode));
    s.data=e;
    s->next=p->next;
    p->next=s;
    return true;
}

同样传入的是第一个节点的地址,要加入节点的位置和要加入的元素,区别就在于加入要在第一个位置插入元素,就需要特别处理一下。

1.3.2. 指定节点的后插操作

在给定节点后插入节点的操作相对简单,示例代码如下:

bool InsertNextNode(LNode *p,int x)
{
    if(p==NULL)
    {
        return false;
    }
    LNode *s=(LNode *)malloc(sizeof(LNode));
    s->next=p->next;
    p->next=s;
    s=>data=x;
    return true;
}

1.3.3. 指定元素的前插操作

在指定元素之前要插入一个元素,最容易想到的办法是将整个链表遍历一遍,之后找到这个元素的前驱节点,转换成后插操作。但是这种办法实现起来较为复杂,所以我们转换一种思维方式,采取新的方法。

我们尝试在指定节点之后先插入一个节点,然后把指定节点的值赋给新节点,之后再把要插入的值赋给旧节点。总而言之就是现在之后插入节点,然后把两个节点的值互换,就相当于将前后节点互换了。示例代码如下:

bool InsertPriorNode(LNode *p,int x)
{
    if(p==NULL)
    {
        return false;
    }
    LNode *s=(LNode *)malloc(sizeof(LNode));
    s->next=p->next;
    p->next=s;
    s->data=p->data;
    p->data=x;
    return true;
}

1.3.4. 按位序删除

1.3.4.1. 带头结点

在带头结点的删除节点操作中,我们需要给函数传递链表的第一个节点的位置,要删除的位置和一个变量用来存储删除节点的元素值。

删除的主要操作是创建一个指针(不分配内存空间)指向要删除的节点,然后让要删除指针前一个节点指向要删除指针的后一个节点,然后将要删除的指针释放内存,从而达到目的。

示例代码如下:

bool ListDelete(List L,int i,int x)
{
    if(i<1)
    {
        return false;
    }
    LNode *p;
    int j=0;
    p=L;
    while(p!=NULL;j<i-1)
    {
        p=p->next;
        j++;
    }
    if(p==NULL)
    {
        return false;
    }
    if(p->next==NULL)
    {
        return false;
    }
    LNode *q=p->next;
    x=q->data;
    p->next=q->next;
    free(q);
}
1.3.4.2. 不带头结点

与不带头结点的插入类似,这里不再赘述。

1.3.5. 指定元素的删除操作

删除操作和带头结点的的相似,指定元素和指定元素的插入相似,这里不再赘述!

你可能感兴趣的:(数据结构,数据结构,链表,C语言)