C:无空头链表

1.链表的结构体以及全局变量声明

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

// 创建Node结构体
struct Node
{
    int a;
    struct Node * pNext;
};

//链表头尾指针
struct Node* g_pHead = NULL; //刚开始链表头部为null(头部:内容,该节点的内容存储)
struct Node* g_pEnd = NULL; //刚开始链表尾部为null      头尾都为null,表示空头链表

 

2.创建链表函数(插尾法和插头法)

①插尾插入法
//创建链表,在链表中增加一个数据,尾添加(g_pEnd发生变化,但是g_pHead不变化,只与第一个传入的Temp有关)
void AddListTail(int a)
{
   //创建一个结点
   struct Node * pTemp = (struct Node*)malloc(sizeof(struct Node));
   //结点数据进行赋值
   pTemp->a = a;
   pTemp->pNext = NULL;
   //链接
   if(NULL == g_pHead || NULL == g_pEnd) //也就是刚开始,头尾都是空,其实写一个就可以,头是空,尾就是空
   {
       g_pHead = pTemp; //此时pTemp的地址既是头也是尾,在之后的调用中,g_pHead始终等于第一次传入的pTemp

  //g_pEnd = pTemp; //g_pEnd赋值成为pTemp新结点
   }
   else//此时链表不再为空
    {
        g_pEnd->pNext = pTemp; //g_pEnd尾部指向新来的结点

  //g_pEnd = pTemp;//指向之后,g_pEnd下移成为pTemp新的结尾结点
    }
    g_pEnd = pTemp;//在每一次调用函数后,g_pEnd都等于此时传入的pTemp          g_pHead    g_pEnd     此时由于是尾插入,g_pEnd地址指向尾部新增,并且之后g_pEnd变为最后一个
}

 

在这里有一点很巧妙,g_pHead = pTemp只是在第一次参数传入的时候赋值,但是在第一次传入参数的最后让g_pEnd也等于pTemp,这样做使在之后,g_pEnd指向下一个值的结构体指针也能被g_pHead使用

同时,如果去打印g_pHead以及g_pEnd的pNext地址可以发现

  (1)g_pHead的pNext永远指向首结点的下一个地址

  (2)g_pEnd的pNext是变化的,因为g_pEnd结点在变化,pNext指向当前的下一跳结点

②插头插入法

//创建链表,在链表中增加一个数据,头添加(g_pHead发生变化,但是g_pEnd不变化,只与第一个传入的Temp有关)

void AddListHead(int a)
{
   //创建一个结点
   struct Node * pTemp = (struct Node*)malloc(sizeof(struct Node));
   //结点数据进行赋值
   pTemp->a = a;
   pTemp->pNext = NULL;
   //链接
   if(NULL == g_pHead || NULL == g_pEnd) //也就是刚开始,头尾都是空,其实写一个就可以,头是空,尾就是空
   {
       g_pHead = pTemp;            //此时pTemp的地址既是头也是尾,在之后的调用中,g_pHead始终等于第一次传入的pTemp

  g_pEnd = pTemp;            //g_pEnd赋值成为第一个传入的pTemp,之后不再改变                    
   }
   else//此时链表不再为空
    {
       g_pTemp->pNext = g_pHead;

  g_pHead = g_pTemp;                                g_pHead    g_pEnd     此时由于是头插入,新增的g_pTemp地址指向头部,并且之后g_pHead变为第一个
    }
}

3.链表的遍历

①全部遍历

void ScanList()
{
    struct Node *pTemp = g_pHead; //初始化*pTemp为头部,如果不初始化,则pTemp为最后一位,在本案例为10
    while(pTemp != NULL)//当链表结点的内容指向null,说明遍历结束
    {
        printf("%d\n", pTemp->a);
        pTemp = pTemp->pNext; //每一次循环指向链表前一个结点
    }
}

在main函数中

直接调用ScanList()

②查询指定结点  通过返回结构体类型,然后通过结构体类型变量pFind来接收

struct Node * SelectNode(int a)
{
    struct Node *pTemp = g_pHead; //初始化*pTemp为头部,如果不初始化,则pTemp为最后一位,在本案例为10
    while(pTemp != NULL)//当链表结点的内容指向null,说明遍历结束
    {
        if (pTemp->a == a)
        {
            return pTemp;
        }
        pTemp = pTemp->pNext; //每一次循环指向链表前一个结点
    }
    return NULL; // 如果找不到返回null
}

在main函数中

    struct Node * pFind = SelectNode(-1);
    if (pFind != NULL)
    {
        printf("%d\n", pFind->a);
    }
    else
    {
        printf("链表中没有该值\n");
    }

 

4.链表清空

①清空列表

void FreeList()
{
    struct Node *pTemp = g_pHead; //初始化*pTemp为头部,如果不初始化,则pTemp为最后一位,在本案例为10
    while(pTemp != NULL)//当链表结点的内容指向null,说明遍历结束
    {
        struct Node * pt = pTemp;
        pTemp = pTemp->pNext; //每一次循环指向链表前一个结点
        free(pt);
    }

    // 头尾清空
    g_pHead = NULL;
    g_pEnd = NULL;
}

在这里,重要的是free要放在最后free

如果free(pTemp)再去指向,则pTemp已经为空,没有指针的作用,如果先指向之后直接free(pTemp)则会导致指向失败

关键是通过结构体指针变量来接收之后,free该变量。最后千万记得去头尾清空,不然g_pHead以及g_pEnd变为野指针,不再是NULL

此时一定要置野指针为NULL

 

5.指定位置插入结点

void AddListRand(int index, int a)
{
    //链表为空
    if (NULL == g_pHead)
    {
        printf("链表没有结点\n");
        return;
    }
    //找位置
    struct Node * pt = SelectNode(index);
    if (NULL == pt)
    {
        printf("没有指定结点\n");
        return;
    }

    //有此结点
    //给a创建结点
    struct Node * pTemp = (struct Node*)malloc(sizeof(struct Node));
    //给结点成员进行赋值
    pTemp->a = a;
    pTemp ->pNext = NULL;

    //链接到链表上
    if (pt == g_pEnd)
    {
        g_pEnd->pNext = pTemp;
        g_pEnd = pTemp;
    }
    else
    {
        //先连
        pTemp->pNext = pt->pNext;
        //后断
        pt->pNext = pTemp;
    }
}

 

6.删除结点

①头删除

void DeleteListHead()
{
    if (NULL == g_pHead)
    {
        printf("链表为NULL,无需释放\n");
        return;
    }
    //记录旧的头
    struct Node*pTemp = g_pHead;
    //头的下一个结点变成新的头
    g_pHead = g_pHead->pNext;
    //释放旧的头
    free(pTemp);
}

   从链表的首项开始删除

②尾删除

void DeleteListEnd()
{
    //判断链表是否为空
    if (NULL == g_pEnd)
    {
        printf("链表为NULL,无需释放\n");
        return;
    }

    //链表不为空
    //链表有1个结点
    if (g_pHead == g_pEnd)
    {
        free(g_pHead);
        g_pHead = NULL;
        g_pEnd = NULL;
    }
    else
    {
        //找到尾巴前一个结点
        struct Node * pTemp = g_pHead;
        while (pTemp->pNext != g_pEnd)
        {
            pTemp = pTemp->pNext;
        }
        //找到了,删除尾巴
        //释放尾巴
        free(g_pEnd);
        //尾巴前移
        g_pEnd = pTemp;
        //尾的下一个指针赋值NULL
        g_pEnd->pNext = NULL;
    }
}

  从列表的尾部开始删除,思想:首先从链表的首项开始遍历,找到最后一项的前一个则停止循环

③指定位置删除

void DeleteListRand(int a)
{
    if (NULL == g_pHead)
    {
        printf("链表为NULL,无需释放\n");
        return;
    }
    //链表有东西,找这个结点
    struct Node* pTemp = SelectNode(a);
    if (NULL == pTemp)
    {
        printf("查无此结点\n");
        return;
    }
    //找到了
    //只有一个结点
    if (g_pHead == g_pEnd)
    {
        free(g_pHead);
        g_pHead = NULL;
        g_pEnd = NULL;
    }
    //有两个结点
    else if (g_pHead->pNext == g_pEnd)
    {
        if (g_pHead == pTemp)
        {
            DeleteListHead();
        }
        else
        {
            DeleteListEnd();
        }
    }
    else //有多个结点
    {
        if (g_pHead == pTemp)
        {
            DeleteListHead();
        }
        else if (g_pEnd == pTemp)
        {
            DeleteListEnd();
        }
        else
        {
            //找删除结点的前一个结点
            struct Node * pT = g_pHead;
            while (pT->pNext != pTemp)
            {
                pT = pT->pNext;
            }
            //找到了
            //链接
            pT->pNext = pTemp->pNext;
            //释放
            free(pTemp);
        }
    }
}

 

posted on 2019-05-14 15:24  zhaoy_shine  阅读(290)  评论(0编辑  收藏  举报

导航