单链表的头插法和尾插法的示例

  单链表是数据结构中最基本的数据结构,单链表有头插法和尾插法,今天有空把这两者做成一个实验示例以作比较。

 

  提示:编译代码是否通过可能与编译器的版本有关,本程序是在 Android 操作系统下的 Compiler C语言编译器下编译通过。

 

  一、头插法和尾插法创建单链表

  增、删、改、查的算法只写了增、查两个,其它省略。

/*
  程序功能:单链表的头插法和尾插法实验示例
  说明:本程序的增删改查功能部分实现
  作者:九天一声啸
  邮箱:[email protected]
  日期:2022.09.09
*/
#include
#include
#include
#include
#include

#define BOOL  int
#define TRUE  1
#define OK    1
#define FALSE 0
#define NO    0
#define ERROR -1
 
//链表长度
 
int length = 16;
 
//TRUE 为自然数列,FALSE 为随机数
bool choose = true;
//bool choose = false;
 
//定义结构体
typedef struct Node 
{
    int data;
    struct Node *next;
    
}Node, *LinkList;
 
LinkList head = NULL;
LinkList end = NULL; //尾插法时使用的结构体指针
 
//头插法函数
void addHead(int data)
{
    LinkList L;
    L = (LinkList)malloc(sizeof(Node));
    L->data = data;
    L->next = head->next;
    head->next = L;
    
}
 
//尾插法函数
void addEnd(int data) {
 
    LinkList L;
    L = (LinkList)malloc(sizeof(Node));
    L->next = NULL;
    L->data = data;
 
    if(NULL == head)
    {
        head = L;
    } else {
        end->next = L;
    }
 
    end = L;
    
}
 
//头插法遍历链表并输出
void showListHead()
{
 
    LinkList tmp;
    tmp = head->next;
 
    while (tmp != NULL)
    {
        printf ("%d ", tmp->data);
        tmp = tmp->next;
    }
    
}
 
//尾插法遍历链表并输出
void showListEnd()
{
 
    LinkList tmp;
    tmp = head;
 
    while(tmp)
    {
        printf("%d ", tmp->data);
        tmp = tmp->next;
    }
    
}
 
int main()
{
 
    int i;
    char str[10];
    BOOL tag;
    srand(time(0));
 
    printf("请输入内容,true 是尾插法;false 是头插法:\n\t->");
    scanf("%s",&str);
 
    /*
      判断输入内容
    
      tag = 1  为true;
      tag = 0  为false
      tag = -1 为error
    */
    if(strcmp(str, "true") == 0)
        tag = TRUE;
    else if(strcmp(str, "false") == 0)
        tag = FALSE;
    else
        tag = ERROR;
 
    if(tag == TRUE) {
 
        //头插法需要这段代码,否则错误
        head = (LinkList)malloc(sizeof(Node));
        head->next = NULL;
 
        //头插法插入数据
        for(i = 0; i < length; i++)
        {
            if(choose == true)
                addHead(i);
            else
                //随机数
                addHead(rand()%100 + 1);
        }
 
        printf("头插法:\n");
 
        //头插法显示
        showListHead();
 
    } else if(tag == FALSE) {
 
        //尾插法插入数据
        for(i = 0; i < length; i++)
        {
            if(choose == true)
                addEnd(i);
            else
                //随机数
                addEnd(rand()%100 + 1);
        }
 
        printf("尾插法:\n");
 
        //尾插法显示
        showListEnd();
 
    } else {
 
        printf("输入错误!");
 
    }
 
    return 0;
    
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

 

程序执行后的效果:

单链表的头插法和尾插法的示例_第1张图片

 

 

  二、完整的单链表数据结构如下:

  在实际应用中,结构体中 data 的数据类型应该是另一个结构体数据类型,即 data 代表数据元素,用于定义姓名、姓别、年龄、地址等等数据项。

/*
  程序功能:单链表的头插法和尾插法实验示例

  说明:本程序的增删改查功能全部实现

  作者:九天一声啸
  邮箱:[email protected]
  日期:2022.09.09
*/
#include
#include
#include
#include
#include
 
//自定义布尔类型
#define BOOL int
#define TRUE  1
#define OK    1
#define FALSE 0
#define ERROR -1
 
BOOL tag;
 
//自定义数据类型 ElemType
#define ElemType int
 
//链表长度
int length = 16;
 
/*
  true 为自然数列,false 为随机数
*/
//bool choose = true;
bool choose = false;
 
//定义结构体
typedef struct Node{

    /*
      data 代表的是数据元素,
      实际应用中 data 应该是另一个结构体,
      并且在结构体中定义数据项。
    */
    int data;          //结点数据
    struct Node *next; //结点指针
}Node, *LinkList;
 
LinkList head = NULL;
LinkList end  = NULL;  //尾插法时使用的结构体指针
 
/*
  功能:头插法算法函数
  
  @param int data 链表数据
  @return void
*/
void addHead(int data){
    LinkList L;
    
    //头插法核心代码 begin
    if(NULL == head)
    {
        //如果没有头节点,创建头节点
        head = (LinkList)malloc(sizeof(Node));
        head->next = NULL;
    }
    else
    {
        L = (LinkList)malloc(sizeof(Node));
        L->data = data;
        L->next = head->next;
        head->next = L;
    }
    //头插法核心代码 end.
}
 
/*
  功能:尾插法算法函数
  
  @param int data 链表数据
  @return void
*/
void addEnd(ElemType data){
    LinkList L;
    
    //尾插法核心代码 begin
    L = (LinkList)malloc(sizeof(Node));
    L->data = data;
    L->next = NULL;
    
    if(NULL == head)
    {
        //如果没有头节点,创建头节点
        head = (LinkList)malloc(sizeof(Node));
        head->next = L;
    }else{
        end->next = L;
    }
    
    end = L;
    //尾插法核心代码 end.
}
 
/*
  功能:返回头结点指针
  
  @return LinkList 自定义结构体指针类型
*/
LinkList getLinkNode()
{
    LinkList L;
    
    L = head->next;
        
    return L;
}
 
/*
  功能:根据输入链表的位置查询链表数据
  
  @param  int num 待查询链表的位置
  @return int
*/
int getElem(int num)
{
    LinkList L;
    int i;
    
    /*
      由于程序是从 0 开始计数的,见 linkCreate() 函数内容,
      我们数数时是从 1 开始的,为了让认知一致,i 应该从 1 开始
      计数。
    */
    i = 1;
    L = getLinkNode();
    
    //循环移动指针到指定位置
    while(L && i < num)
    {
        //指针向后移动一位
        L = L->next;
        ++i;
    }
    
    if(!L || i > num)
        return ERROR;
    
    //查询节点数据的核心代码,直接返回数据即可
    return L->data;
}
 
/*
  功能:根据数据位置修改链表
  
  @param  int num  待查询链表的位置
  @param  int data 待修改的数据
  @return BOOL     自定义的布尔类型
*/
BOOL changeElem(int num, ElemType data)
{
    LinkList L;
    int i;
    i = 1;
    L = getLinkNode();
    
    //循环移动指针到指定位置
    while(L && i < num)
    {
        //指针向后移动一位
        L = L->next;
        ++i;
    }
    
    if(!L || i > num)
        return ERROR;
        
    //修改节点数据的核心代码 begin
    L->data = data;
    //修改节点数据的核心代码 end
    
    return OK;
}
 
/*
  功能:根据输入链表的位置插入链表节点
  
  @param  int num  待插入链表的位置
  @param  int data 待插入的数据
  @return BOOL     自定义的布尔类型
*/
BOOL insertList(int num, ElemType data)
{
    LinkList L, s;
    int j;
    j = 1;
    L = getLinkNode();
    
    //循环移动指针到指定位置
    while(L && j < num)
    {
        //指针向后移动一个节点
        L = L->next;
        ++j;
    }
    
    if(!L || j > num)
        return ERROR;
    
    //插入节点和数据的核心代码 begin
    s = (LinkList)malloc(sizeof(Node));
    s->data = data;
    s->next = L->next;
    L->next = s;
    //插入节点和数据的核心代码 end
    
    return OK;
}
 
/*
  功能:根据输入链表的位置删除链表的节点
  
  @param int num   要删除链表的位置
  @return ElemType 自定义类型
*/
ElemType deleteList(int num)
{
    LinkList L, s;
    ElemType tmp;
    int j;
    
    /*
        由于程序是从0开始计数的,见 linkCreate() 函数内容,
      我们数数时是从1开始的,为了让认知一致,j 应该从 1 开始
      计数;
        再由于,要删除的数据是从查询到的位置的下一个位置开始删
      的,所以这里 j 再加1,即 j = 2。
    */
    j = 2;
    
    /*
      双向链表有前驱指针可以在删除第一个节点时,直接指向头节点。然而,
      由于单向链表没有前驱指针,在指向头节点后的第一个节点时,在删除
      第一个节点时删除不了第一个节点,还是删除的是第二个节点,因此
      这里如果要删除第一个节点的话,要把指针指向头节点。
    */
    if (num == 1)
    
        /*
          删除第一个节点时,指针指向头节点,头节点没有数据内容,
          即 head->data 为空。
        */
        L = head;
    else
        
        /*
          删除其它节点时,指针指向头节点后有数据内容的第一个节点,
          即 head->next->data 是 head 头节点后,第一个节点的数据。
          注意:head->next 是 head 节点后的第一个节点。
        */
        L = getLinkNode();
    
    //循环移动指针到指定位置
    while(L && j < num)
    {
        //指针向后移动一个节点
        L = L->next;
        j++;
    }
    
    if((!L || j > num) && num != 1)
        return ERROR;
    
    //删除节点的核心代码 begin
    s = L->next ;
    L->next = s->next ;
    tmp = s->data;
    free(s);
    //删除节点的核心代码 end
    
    return tmp;
}
 
/*
  功能:链表的整表删除
  
  @return BOOL 自定义布尔类型
*/
BOOL allDeletList()
{
    LinkList L, s;
    s = head->next;
    
    //整表删除的核心代码 begin
    while(s)
    {
        L = s->next;
        free(s);
        s =L;
    }
    //整表删除的核心代码 end
    
     //TRUE:头插法;FALSE:尾插法
    if(tag == TRUE)
        head->next = NULL;
    else
        head = NULL;
    
    return OK;
}

//----------- 以上是单链表的算法实现
//----------- 以下是辅助函数和程序,不必深究。

/*
  功能:头插法遍历链表并输出
  
  @return void
*/
void showListHead()
{
    LinkList tmp;
    tmp = head->next;
    
    while (tmp != NULL)
    {
        printf ("%d ", tmp->data);
        tmp = tmp->next;
    }
}
 
/*
  功能:尾插法遍历链表并输出
  
  @return void
*/
void showListEnd()
{
    LinkList tmp;
    bool flag = false ;
    
    tmp = head;
    
    while(tmp != NULL)
    {
        if (flag)
            printf("%d ", tmp->data);
        else 
            flag = true;
            
        tmp = tmp->next;
    }
}
 
/*
  功能:遍历单链表并显示
  
  @return void
*/
void showList()
{
    //TRUE:头插法;FALSE:尾插法
    if(tag == TRUE)
    {
        //头插法遍历显示
        showListHead();
    }
    else if(tag == FALSE)
    {
        //尾插法遍历显示
        showListEnd();
    }
}
 
/*
  功能:创建单链表
  
  @return BOOL 自定义的布尔类型
*/
BOOL linkCreate()
{
    
    int i;
    char str[10];
    srand(time(0));
    
    printf("1.请输入内容,true 是头插法;false 是尾插法:\n\t-> ");
    scanf("%s",&str);
    
    /*
      判断输入内容
    
      tag = 1    为true;
      tag = 0    为false
      tag = -1   为error
    */
    if(strcmp(str, "true") == 0)
        tag = TRUE;
    else if(strcmp(str, "false") == 0)
        tag = FALSE;
    else
        tag = ERROR;
    
    //TRUE:头插法;FALSE:尾插法;ERROR:错误
    if(tag == TRUE){
    
        //头插法插入数据
        for(i = 0; i < length; ++i)
        {
            if(choose == TRUE)
                addHead(i);
            else
                //随机数
                addHead(rand()%100 + 1);
        }
        
        printf("头插法:\n");
        
        //头插法遍历显示
        showListHead();
        
        return OK;
        
    }else if(tag == FALSE){
        
        //尾插法插入数据
        for(i = 0; i < length; i++)
        {
            if(choose == TRUE)
                addEnd(i);
            else
                //随机数
                addEnd(rand()%100 + 1);
        }
    
        printf("尾插法:\n");
        
        //尾插法遍历显示
        showListEnd();
        
        return OK;
        
    }else{
    
        printf("输入错误!");
        return ERROR;
        
    }
    
}
 
/*
  功能:根据输入链表的位置查询数据
  
  @return void
*/
void linkQuery()
{
    int input;
    printf("\n\n2.请输入要查询的位置:\n\t-> ");
    scanf("%d", &input);
    
    if(input <= length)
    {
        int getNum = getElem(input);
        printf("\n获取到的值:%d", getNum);
    }else{
        printf("\n╳ 没有这么长的链表!");
    }
}
 
/*
  功能:根据输入链表的位置修改数据
  
  @return void
*/
void linkRevise()
{
    int input, data;
    printf("\n\n3.请输入要修改的位置和数据,两个数据用空格隔开:\n\t-> ");
    scanf("%d%d", &input, &data);
    
    if(input <= length)
    {
        changeElem(input, data);
    
        printf("\n链表修改后的值:\n");
        
        showList();
    }else{
        printf("\n╳ 没有这么长的链表!");
    }
}
 
/*
  功能:根据输入链表的位置插入数据
  
  @return void
*/
void linkInsert()
{
    int input, data;
    printf("\n\n4.请输入要插入的位置和数据,两个数据用空格隔开:\n\t-> ");
    scanf("%d%d", &input, &data);
    
    if(input <= length)
    {
        int ret = insertList(input, data);
    
        if(ret == OK)
            length++;
            
        printf("\n链表修改后的值:\n");
        
        showList();
    }else{
        printf("\n╳ 没有这么长的链表!");
    }
}
 
/*
  功能:根据输入链表的位置删除数据
  
  @return void
*/
void linkDelete()
{
    int input, data, tmp;
    printf("\n\n5.请输入要删除的位置:\n\t-> ");
    scanf("%d", &input);
    
    if(input <= length)
    {
        tmp = deleteList(input);
        
        if(tmp != ERROR)
            length--;
    
        printf("\n->被删除的值:%d\n\n", tmp);
        printf("\n链表删除后的值:\n");
        
        showList();
    }else{
        printf("\n╳ 没有这么长的链表!");
    }
}
 
/*
  功能:主函数入口
  
  @return int
*/
int main()
{
    //链表操作:创建链表
    BOOL val = linkCreate();
    
    if(val != ERROR)
    {
        printf("\n--------------------------------\n");
        
        //链表查询
        linkQuery();
        printf("\n--------------------------------\n");
    
        //链表修改
        linkRevise();
        printf("\n--------------------------------\n");
        
        //链表插入
        linkInsert();
        printf("\n--------------------------------\n");
 
        //链表删除
        linkDelete();
        printf("\n--------------------------------\n");
        
        //整表删除
        allDeletList();
        printf("\n6.整表删除后的链表数据:");
        showList();
    }
    return 0;
}
    
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

 

程序执行后的效果:

单链表的头插法和尾插法的示例_第2张图片

 

 

三、结构体定义的完整的数据结构演示如下:

  把结构体的数据域写全,并用最原始的方法来演示一下单链表。

#include
#include
#include
#include

//结构体定义的数据域
typedef struct myData{
    int  id;
    char name[20];
    short int  sex; //0为男;1为女
    char address[100];
}myData;

//结构体定义的节点
typedef struct Node{

    //数据域,这里用结构体变量,不能直接用指针,
    //如果没有变量,指针就不能指向变量的地址。
    struct myData data;
    
    //指针域
    struct Node *next;
}Node, *LinkList;

//性别判断
void determine(short int flag, char* e)
{
    if(flag == 0)
        strcpy(e, "男");
    else if(flag == 1)
        strcpy(e, "女");
    else
        strcpy(e, "未知");
}

//主函数入口
int main()
{
    char ssex[10] ="未知";
    
    //1.结构体变量,静态分配内存
    Node link1; 
    
    //赋值
    link1.data.id = 12;
    strcpy(link1.data.name, "孙悟空");
    link1.data.sex = 0;
    strcpy(link1.data.address, "花果山水帘洞");
    
    //性别判断
    determine(link1.data.sex, ssex);
    
    printf("编号:%d\n姓名:%s\n姓别:%s\n地址:%s\n\n", link1.data.id, link1.data.name, ssex, link1.data.address);
  
    //2.malloc() 函数动态分配内存
    LinkList link2 = (LinkList)malloc(sizeof(Node));
    
    //赋值
    link2->data.id = 13;
    strcpy(link2->data.name, "嫦娥");
    link2->data.sex = 1;
    strcpy(link2->data.address, "月亮广寒宫");
    
    //性别判断
    determine(link2->data.sex, ssex);
    
    printf("编号:%d\n姓名:%s\n姓别:%s\n地址:%s\n\n", link2->data.id, link2->data.name, ssex, link2->data.address);
  
    //3.如果把上面的两个结构体变量组合成一个单向链表
    
    /*
      用这种最原始的方法,才能看清楚它们的关系
    
      这里 link1 是结构体变量,
      而 link2 是 malloc() 直接返回的就是指针,非常方便。
    */
    Node L; //内存分配一个头节点
    Node* head = &L;
    //这里等效于:Node* head = (Node*)malloc(sizeof(Node));
    
    head->next = &link1;
    head->next->next = link2;
    head->next->next->next = NULL; //单链表要求尾节点的 next 为空
    /*
      单链表生成完毕,这是最直观的单链表了,上面的五段代码看明白了,
      单链表的算法已就能写出来了。
    
      说明:
      1.一般要求头节点除了 next 指针域是有效数据,data 数据域是无效数据,不用管它;
      2.也可以在头节点放置链表长度,这时头节点要单独定义一个结构体,指针域不变,数据
        域为:int length; 只需把 next 指向其它有数据的节点即可。这样的好处就是在
        获取单链表的长度时,不用遍历整个链表,也就是时间复杂度由 O(n) 变为了 O(1),
        但是,也可以不需要头节点。
      3.一个 next 其实就是一个有数据内容的节点,而最后的尾节点为 NULL 而已。
    */
    
    printf("以下内容是从单链表输出的:\n\n");
    
    //显示第一个有数据的节点内容
    determine(head->next->data.sex, ssex);
    printf("编号:%d\n姓名:%s\n姓别:%s\n地址:%s\n\n", head->next->data.id, head->next->data.name, ssex, head->next->data.address);
    
    //显示第二个有数据的节点内容
    determine(head->next->next->data.sex, ssex);
    printf("编号:%d\n姓名:%s\n姓别:%s\n地址:%s\n", head->next->next->data.id, head->next->next->data.name, ssex, head->next->next->data.address);

    return 0;
}
    
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

 

  程序执行后的效果:

单链表的头插法和尾插法的示例_第3张图片

 

 

 

 

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