11-链表

43 阅读11分钟

知识点一:链表基本概念

定义:链表是一种物理存储上非连续,数据元素的逻辑顺序通过链表中的指针链接次序,实现的一种线性存储结构

特点:链表由一系列节点( 链表中每一个元素称为节点)组成,节点在运行时动态生成(malloc) ,每个节点包括两个部分:

    1)存储数据元素的数据域          2)存储下一个节点地址的指针域

链表分类及分析:

链表分类及分析

知识点二:链表节点定义

构成:链表由一个个节点构成,每个节点一般采用结构体的形式组织

//单向链表定义
typedef struct stu
{
    //数据域(自定义)
    int num;
    char name[32];
    float score;
    //指针域
    struct stu *next;	//保存 下一个节点的地址
}STU;

//双向链表定义
typedef struct stu
{
    int num;
    char name[32];
    float score;
    
    struct stu *next;
    struct stu *pre;
}STU;

知识点三:链表遍历

链表的基本概念
typedef struct stu
{
    //数据域(自定义)
    int num;
    char name[32];
    float score;
    //指针域
    struct stu *next;	//保存 下一个节点的地址
}STU;

//单向链表遍历
void test()
{
    STU data1 = {100, "德玛", 59};
    STU data2 = {101, "小法", 79};
    STU data3 = {102, "小泡", 49};
    STU data4 = {103, "盲僧", 99};
    STU data5 = {104, "快乐", 89};
    
    //链表头
    STU *head = NULL;
    head = &data1;
    data1.next = &data2;
    data2.next = &data3;
    data3.next = &data4;
    data4.next = &data5;
    data5.next = NULL;
    
    pb = head;
    while(pb != NULL)
    {
        printf("%d %s %f\n", pb->num, pb->name, pb->score);
         //pb == head:即pb指向第一个节点,pb->next则为下一个节点地址
        pb = pb->next;	//pb指向下一个节点
    }
}

//双向遍历
void print_link(STU *head)
{
    //判断链表是否存在
    if(head == NULL)
    {
    	printf("link not found\n");
    }
    else	//链表存在
    {
        STU *pb = head;	//pb指向头结点
        STU *pf = head‐>pre;	//pf指向了尾节点
        do
        {
            if(pb == pf)	//相遇 只需要打印pf或pb中任何一个信息就够了
            {
                printf("num=%d,name=%s,age=%d\n", pb->num,pb->name,pb->age);
                break;
            }
            printf("num=%d,name=%s,age=%d\n", pb->num,pb->name,pb->age);
            printf("num=%d,name=%s,age=%d\n", pf->num,pf->name,pf->age);

            pb = pb->next;//next方向的移动
            pf = pf->pre;//pre方向的移动
        }while( pb‐>pre != pf );	//pf和pb不能 擦肩而过
    }
}

知识点四:链表插入

分析有无带头

有无带头结点分析

1、链表第一个节点之前插入

第一个节点插入

main.c

#include<stdio.h>
#include<string.h>
#include "link.h"
void stu_help(void);
int main(int argc,char *argv[])
{
    //定义一个链表头 注意 一定要赋值为NULL
    STU *head = NULL;

    stu_help();

    while(1)
    {
        char cmd[32]="";
        printf("请输入操作指令:");

        scanf("%s",cmd);
        if(strcmp(cmd, "help") == 0)
        {
        	stu_help();
        }
        else if(strcmp(cmd, "insert") == 0)
        {
            STU tmp;
            printf("请输入需要插入的数据:");
            scanf("%d %s %f",&tmp.num, tmp.name, &tmp.score);

            //将tmp数据 插入到head所指向的链表中
            head = insert_link(head, tmp);
        }
        else if(strcmp(cmd,"print") == 0)
        {
            print_link(head);
        }
        else if(strcmp(cmd,"search") == 0)
        {
        	printf("‐‐‐‐‐search‐‐‐‐‐‐\n");
        }
        else if(strcmp(cmd,"delete") == 0)
        {
        	printf("‐‐‐‐‐delete‐‐‐‐‐‐\n");
        }
        else if(strcmp(cmd,"free") == 0)
        {
        	printf("‐‐‐‐‐free‐‐‐‐‐‐\n");
        }
        else if(strcmp(cmd,"quit") == 0)
        {
            head = free_lin(head);
            break;
        }
    }
    return 0;
}

void stu_help(void)
{
    printf("################################\n");
    printf("#help:打印帮助信息 #\n");
    printf("#insert:插入链表节点 #\n");
    printf("#print:遍历链表节点信息 #\n");
    printf("#search:查询链表节点 #\n");
    printf("#delete:删除链表节点 #\n");
    printf("#free:释放链表 #\n");
    printf("#quit:退出 #\n");
    printf("################################\n");
    return;
}

link.c

#include<stdio.h>
#include<stdlib.h>	//calloc
#include"link.h"
STU* insert_link(STU *head, STU tmp)
{
    //1、从堆区申请一个待插入的节点空间
    STU *pi = (STU *)calloc(1,sizeof(STU));
    if(pi == NULL)	//如果空间开辟成功则!=NULL
    {
        perror("calloc");	//打印错误信息
        return head;
    }

    //2、将tmp的值 赋值 给*pi
    *pi = tmp;
    pi‐>next = NULL;//注意

    //3、将pi插入到链表中
    if(head == NULL)	//链表不存在
    {
        head = pi;
    }
    else	//链表存在(头部之前插入)
    {
        //1、让pi 指向就的头
        pi->next = head;

        //2、head指向新的头节点
        head = pi;
    }
    return head;
}

void print_link(STU *head)
{
    if(head == NULL)	//链表不存在
    {
        printf("link not find\n");
        return;
    }
    else
    {
        STU *pb = head;
        while(pb != NULL)
        {
            printf("%d %s %f\n", pb->num, pb->name,pb->score);
            //pb == head:即pb指向第一个节点,pb->next则为下一个节点地址
            pb = pb->next;	//pb指向下一个节点地址
		}
	}
	return;
}

link.h

//防止头文件重复包含
#ifndef __LINK_H__
#define __LINK_H__

//链表节点类型 定义
typedef struct stu
{
    //数据域
    int num;
    char name[32];
    float score;

    //指针域
    struct stu *next;
}STU;

extern STU* insert_link(STU *head, STU tmp);
extern void print_link(STU *head);

#endif

2、链表最后一个节点插入

最后一个节点插入
//链表的尾部插入
STU* insert_link(STU *head, STU tmp)
{
    //1、申请待插入的节点5 STU *pi = (STU *)calloc(1,sizeof(STU));
    if(pi == NULL)
    {
        perror(calloc);
        return head;
    }

    //2、将tmp的数据 赋值到 *pi
    *pi = tmp;
    pi->next = NULL;

    //3、将节点插入到链表的尾部
    if(head == NULL)	//链表不存在
    {
        head = pi;
        return head;
    }
    else	//链表存在
    {
        //a、寻找链表的尾节点
        STU *pb = head;
        while(pb->next != NULL)	//如果不是尾节点
        	pb = pb‐>next;	//pb就指向下一个节点

        //b、用尾结点 pb 链接上 插入的节点pi
        pb->next = pi;
        return head;
    }
    return head;
}

3、链表有序插入

根据顺序插入选择一种方式插入:第一个、中间、最后一个插入

有序插入
//链表的有序插入 以num的顺序为准(小‐‐‐>大)
STU* insert_link(STU *head, STU tmp)
{
    //1、给待插入的节点pi 申请 堆区空间
    STU *pi = (STU *)calloc(1,sizeof(STU));
    if(pi == NULL)
    {
        perror("calloc");
        return head;
    }

    //2、将tmp的内容 赋值给 *pi
    *pi = tmp;
    pi‐>next = NULL;

    //3、链表节点pi的插入
    if(head == NULL)	//链表不存在
    {
        head = pi;
        return head;
    }
    else	//存在
    {
        //a、寻找插入点
        STU *pb = head, *pf = head;
        //pb->num 小于 pi->num 才会移动
        while(pb->num < pi->num && pb->next != NULL)
        {
            pf = pb;	//pf先保存pb地址
            pb = pb‐>next;	//pb-next即下一个节点地址赋给pb
        }

        //b、插入点的判断
        if(pb->num >= pi->num)	//头部 中部插入
        {
            if(pb == head)	//头部之前插入
            {
                pi->next = head;
                head = pi;
                return head;
            }
            else	//中部插入
            {
                pf->next = pi;
                pi->next = pb;
                return head;
            }
        }
        else	//尾部插入
        {
            pb->next = pi;
            return head;
        }
    }
    return head;
}

4、双向循环链表头部插入

双向循环链表头部插入
//头部之前插入
STU* insert_link(STU *head, STU tmp)
{
    //1、为插入的节点pi申请空间
    STU *pi = (STU *)calloc(1, sizeof(STU));
    //2、将tmp的值 赋值给 *pi7 *pi = tmp;
    //3、判断链表是否存在
    if(head == NULL)	//链表不存在
    {
        head = pi;
        pi->next = head;
        pi->pre = head;
    }
    else	//链表存在(头部之前插入)
    {
        pi->next = head;	//新头结点的next指向旧的头结点
        pi->pre = head->pre;	//新头结点的pre指向尾结点
        head->pre->next = pi;	//让尾结点的next指向新的头结点
        head->pre = pi;	//旧的头结点pre指向新的头
        head = pi;	//head指向新的头
    }
    return head;
}

知识点五:链表查询

链表查询

mian.c(整个程序在第一个节点之前插入中)

else if(strcmp(cmd, "search") == 0)
{
	char name[64] = 0;
	STU *ret = NULL;
	printf("请输入要查找的用户名:");
	scanf("%s", name);
	
	ret = search_link(head, name);
	if(ret != NULL)
	{
		prinf("num = %d, name = %s, score = %f\n", ret->num, ret->name, ret->score);
	}
}

link.c

STU* search_link(STU *head, char *name)
{
    //1、判断链表是否存在
    if(head == NULL)	//不存在
    {
        printf("link not found\n");
        return NULL;
    }
    else	//链表存在
    {
        STU *pb = head;

        //逐个将节点中的name 和 name比较 如果不相等 pb=pb->next
        while(strcmp(pb->name, name) != 0 && pb->next != NULL)
        	pb = pb‐>next;

        //判断是否找到
        if(strcmp(pb->name, name) == 0)	//找到
            return pb;
        else	//没找到
            return NULL;
    }
    return NULL;
}

双向链表查询

STU* search_link(STU *head, char *name)
{
    //判断链表是否存在
    if(head == NULL)
    {
    	return NULL;
    }
    else	//链表存在
    {
        STU *pb = head;//指向头结点
        STU *pf = head‐>pre;//指向的是尾节点
        printf("pb = %p\n", pb);
        printf("pf = %p\n", pf);
        printf("pb‐>pre = %p\n", pb‐>pre);

        while( (strcmp(pb->name, name) != 0) && (strcmp(pf->name, name) != 0) && (pb != pf) )
        {
            printf("##pb‐>name=%s##\n",pb‐>name);
            printf("##pf‐>name=%s##\n",pf‐>name);
            pb = pb->next;	//next方向移动
            pf = pf->pre;	//pf方向移动

            if(pb‐>pre == pf)
            {
            	break;
            }
        }

        if(strcmp(pb->name,name) == 0)
        {
			return pb;
        }
        else if(strcmp(pf->name,name) == 0)
        {
        	return pf;
        }
    }
    return NULL;
}

知识点六:删除链表节点

单向链表:删除思路与有序插入相似,可参考

main.c

else if(strcmp(cmd,"delete") == 0)
{
    char name[32] = "";
    printf("请输入将要删除的姓名:");
    scanf("%s",name);
    head = detele_link(head,name);
}

link.c

STU* detele_link(STU *head,char *name)
{
    //1、判断链表是否存在
    if(head == NULL)	//不存在
    {
        printf("link not found\n");
        return head;
    }
    else	//存在
    {
        //2、寻找删除点
        STU *pf=head, *pb = head;
        while(strcmp(pb‐>name,name)!=0 && pb‐>next != NULL)
        {
            pf = pb;
            pb = pb->next;
        }

        //3、找到删除点
        if(strcmp(pb->name, name)==0)	//找到删除点
        {
            //4、判断删除的位置
            if(pb == head)	//删除头结点
            {
                head = pb‐>next;
                free(pb);
            }
            else	//中部 或 尾部节点
            {
                pf->next = pb->next;
                free(pb);	//释放pb节点
            }
            printf("已成功删除%s的相关节点\n",name);
            return head;
        }
        else	//没找到删除点
        {
        	printf("链表中没有%s相关的数据节点信息\n",name);
        }
    }
    return head;
}

双向链表删除节点

双向链表删除节点
STU* delete_link(STU *head, int num)
{
    //判断链表是否存在
    if(head == NULL)
    {
        printf("link not found\n");
        return head;
    }
    else
    {
        STU *pb = head;	//指向头节点
        STU *pf = head‐>pre;	//指向尾节点

        //逐个节点寻找删除点
        while((pb‐>num != num) && (pf‐>num != num) && (pf != pb))
        {
            pb = pb‐>next;	//next方向移动
            pf = pf‐>pre;	//pre方向移动
            if(pb‐>pre == pf)
            break;
        }
        if(pb->num == num)	//删除pb指向的节点
        {

            if(pb == head)	//删除头节点
            {
                if(head == head->next)	//链表只有一个节点
                {
                    free(pb);
                    head = NULL;
                }
                else
                {
                    head->next->pre = head->pre;
                    head->pre->next = head->next;
                    head = head->next;
                    free(pb);
            	}
            }
            else	//删除中尾部节点
            {
                pb->pre->next = pb->next;
                pb->next->pre = pb->pre;
                free(pb);
            }
        }
        else if(pf->num == num)	//删除pf指向的节点
        {
            if(pf == head)	//删除头节点
            {
                if(head == head‐>next)	//链表只有一个节点
                {
                    free(pf);
                    head = NULL;
                }
                else
                {
                    head‐>next‐>pre = head‐>pre;62 head‐>pre‐>next = head‐>next;
                    head = head‐>next;
                    free(pf);
                }
        	}
            else	//删除中尾部节点
            {
                pf‐>pre‐>next = pf‐>next;
                pf‐>next‐>pre = pf‐>pre;
                free(pf);
            }
        }
        else
        {
        	printf("未找到%d相关的节点信息",num);
        }
    }
    return head;
}

知识点七:链表释放

链表释放

main.c

else if(strcmp(cmd,"free") == 0)
{
    head = free_lin(head);
}

link.c

STU* free_link(STU *head)
{
    //1、判断链表是否存在
    if(head == NULL)	//不存在 
    {
        printf("link not found\n");
        retrun head;
    } 
    else	//存在 
    {
        STU *pb = head;
        //逐个节点释放
        while(pb != NULL)
        {
            //head保存下一个节点的位置
            head = pb->next;
            //释放pb指向的节点
            free(pb);
            //pb指向head
            pb = head; 
        }
        printf("链表已经释放完毕\n"); 
        return head;
    }
    return head;
}

知识点八:链表逆序

链表逆序

mian.c

else if(strcmp(cmd,"reverse") == 0)
{
    head = reverse_link(head);
}

link.c

STU* reverse_link(STU *head)
{
    //1、判断链表是否存在
    if(head == NULL)	//不存在 
    {
        printf("link not found\n");
        retrun head;
    } 
    else//存在 
    {
        //int *p,num;     //p为int *, num为int
        STU *pb,*pr;    //pb为STU *,pr 为STU *
        
        //pb保存head->next ( 原因head->next会置NULL)
        pb = head->next;
        
        //将head->next置NULL(原因:头节点变尾节点)
        head->next = NULL;
        
        while(pb != NULL)
        {
            //pr 保存pb->next ( 原因:pb->next会指向head)
            pr = pb -> next;
            //pb->next指向head ( 原因:逆转方向)
            pb->next = head ;
            //保存逆转方向的代码可以重复执行
            head = pb;
            pb = pr;
        }
        return head;
    }
    return head;
}

知识点九:链表排序

选择法排序(以数组实现):

数组排序
#include<stdio.h>
int main()
{
    int arr[10] = {0};
    int n = sizeof(arr)/sizeof(arr[0]);
    int i = 0,j = 0,min = 0;
    printf("请输入%d个int数据\n", n);
    for(i = 0; i<n; i++)
    {
        scanf("%d",arr+i);
    }
    for(i = 0;i < n-1;i++)
    {
        for(min = i,j = min+1;j < n;j++)
        {
            if (arr[min] > arr[j])
            min = j;
        }
        if (min != i)
        {
            int tmp = 0;
            tmp = arr[i];
            arr[i] = arr[min];
            arr [min] = tmp;
        }
    }
    for(i = 0;i < n;i++) 
    {
        printf("%d ",arr[i]);
    }
    printf("\n");
    return 0;
}

选择法排序(以单向链表实现):

思路与数组排序相似,参考数组

void sort_link(STU *head)
{
    //1、判断链表是否存在
    if(NULL == head)
    {
        printf("link not found\n");
        return;
    }
    else
    {
        STU *p_i = head;	//i=0
        while(p_i‐>next != NULL)	//i<n‐1 外层循环
        {
            STU *p_min = p_i;	//min = i;
            STU *p_j = p_min‐>next;	//j = min+1
            while(p_j != NULL)	//j<n 内层循环
            {
                //寻找成员num最小值的 节点
                if(p_min->num > p_j->num)	//if(arr[min] > arr[j])
                	p_min = p_j;	//min = j
                p_j = p_j->next;	//j++
            }
            if(p_min != p_i)	//min != i
            {
                //只交换数据域(1、节点内容整体交换 2、只交换指针域)
                //1、节点内容整体交换(数据域交换第1次 指针域交换第1次)
                STU tmp;
                tmp = *p_i;
                *p_i = *p_min;
                *p_min = tmp;

                //2、只交换指针域(指针域交换第2次)
                tmp.next = p_i‐>next;
                p_i‐>next = p_min‐>next;
                p_min‐>next = tmp.next;
            }
            p_i = p_i->next;	//i++
        }
    }
}