【数据结构】线性表(二)—— 单链表(C语言版)

线性表的链式存储结构

定义:

用一组任意的存储单元存储线性表的数据元素,(这组存储单元可以是连续的,也可以是不连续的),逻辑上相邻的元素存储位置不一定相邻。

链式结构中,除了要存储数据元素信息外,还要存储它的后继元素的存储地址。

注:以下示例中结点只包含一个指针域,所以称为单链表

在这里插入图片描述

结点:包含两个域:数据域(存储数据元素信息)、指针域(存储直接后继存储位置)

头指针:链表中第一个结点的存储位置,之后的每一个结点,其实就是上一个的后继指针指向的位置,最后一个结点的指针为null。

头结点:在单链表的第一个结点前附设一个结点,其数据域可不存储信息,可存储附加信息。若指针域为空,则线性表为空表。头结点的指针域存储指向第一个结点的指针

头指针与头结点的异同:
头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针
头指针具有标识作用,所以常用头指针冠以链表的名字
无论链表是否为空,头指针均不为空,头指针是链表的必要元素
头结点不一定是链表必须要素
引入头结点的优点:
  1)使得在链表的第一个位置上的操作和在表的其他位置上的操作一致。

  2)空表和非空表的处理一致。

不带有头结点的单链表

在这里插入图片描述

带有头结点的单链表

在这里插入图片描述
空链表图示:

在这里插入图片描述

单链表的实现

数据元素结构体定义
typedef struct{
    char  no[20];
    char  name[50];
    float price;
}Book;

typedef Book ElemType;
单链表的结点类型
typedef struct LNode
{
    ElemType    data;
    struct LNode *next;
}LNode, *LinkList;          //LinkList为指向结构体LNode的指针类型
单链表的初始化

初始化操作是创建一个只有一个头结点的空链表,

void InitLinkList (LinkList *head)
{
    (*head) = (LinkList)malloc(sizeof(LNode));  //注意在函数中,(*head) 代表head指向的结点
    (*head)->next = NULL;
}
单链表的读取

· 获得链表第i个数据的算法思路:

    -声明一个结点p指向链表第一个结点,初始化 j 从 0 开始。

    -当j < i 时,就遍历链表,让p的指针向后移动,不断指向下一结点,j++。 // 注意保持 j 和 p 的同步。

    -若到链表末尾 p 为空,则说明第i个元素不存在。

    -否则查找成功,返回结点p的数据。
Status GetElem (LinkList head, int i, ElemType *e)
{
    int j=0;
    LinkList p;
    p = head;
    while (j < i && p != NULL)
    {
        j++;
        p = p->next;
    }
    if (!p || j>i)
    {
        printf("i is error");
        return ERROR;
    }
    else
    {
        *e = p->data;
        return OK;
    }
}
单链表的插入

单链表第i个数据插入结点的算法思路:

声明一结点 p 指向链表第一个结点,初始化 j 从 0 开始
当 j < (i - 1) 时,就遍历链表,让p的指针向后移动,不断指向下一结点,j ++;
若到链表末尾p为空,则说明第i个元素不存在
否则查找成功,在系统中生成一个空结点 temp
将数据元素 e 赋值给 temp -> data
单链表的插入标准语句 temp -> next = p->next; p->next=temp ;

在这里插入图片描述

// 注意 j 与 p 的指向必须一致

Status InsertList (LinkList *head, int i, ElemType elem)
// 注:也可以使用head, 因为head指向头结点,所指向的头结点的地址未发生改变
{
    LinkList p;
    p = (*head);       // p 指向头结点
    int j = 0;
    while (j < (i-1) && p->next != NULL )  // j < (i-1) : j在第 i-1 的位置停止   // 注意 j 与 p 的指向必须一致
    {
        j++;
        p = p->next;
    }
    if (!(p->next) || j> (i-1))
    {
        printf("i is error");
        return ERROR;
    }
    else
    {
        LinkList temp;
        temp  = (LinkList)malloc(sizeof(LNode));
        temp->data = elem;

        temp->next = p->next;
        p->next = temp;

        return OK;
    }

/*
    if (p = NULL)
    {
        printf("LinkList is Empty");

    }// 可能插入在空表中,故不能作为依据;*/
}
单链表的删除

在这里插入图片描述

单链表第i个数据删除结点的算法思路:

声明一结点指针 p 指向链表第一个结点,初始化 j 从 0 开始
当 j < (i-1) 时,就遍历链表,让 p 的指针向后移动,不断指向下一个节点,j ++;
若在 位置序号为 [ 1, i-1 ] 的遍历过程中 p指向的结点均 不为空,则说明第 [ 1, i-1 ] 个元素均存在;此时通过  判断 p->next == NULL ,判断 第 i 个元素是否存在;若存在,

则申请指向结点的指针 temp , 令 temp = p-> next ;
将 temp 所指向的结点的数据域的值 data 赋值给 *e; 
令 p->next = p->next->next;  也可以为 p->next = temp->next;
最后释放 temp 结点;
Status DeleteList (LinkList *head, int i, ElemType *e)
{
    LinkList p;
    p = (*head);
    int j = 0;
    while (j < (i-1) && p != NULL)
    {
        j ++;
        p = p->next;
    }
    if (!(p->next) || j > (i-1))//  (j!=i-1)
    {
        printf("i is error\n");
        return ERROR;
    }
    else
    {
        if (p->next != NULL)//判断是否有第 i 个结点
        {
            LinkList temp;
            temp = p->next;
            *e = temp->data;
            p->next = p->next->next;
            free(temp);
            return OK;
        }
        else
        {
            printf("i is error, \n");
            return ERROR;
        }
    }
}

单链表在查询、插入和删除操作上的时间复杂度都是O(n),但是如果需要从第i个位置,插入10个元素,对于顺序存储结构意味着,每一次插入都需要移动n-i个元素,每次都是O(n),而单链表,只需要在第一次时,找到第i个位置的指针,此时为O(n),接下来的时间复杂度都是O(1),所以对于插入或者删除数据越频繁的操作,单链表的效率优势就越是明显

单链表整表的创建

单链表整表创建的算法思路:

声明一结点p和计数器变量i
初始化一空链表L
让L的头结点的指针指向NULL,即建立一个带头结点的单链表

循环:
    生成一新结点赋值给p
    随机生成一数字赋值给p的数据域p->data
    将p插入到头结点与前一新结点之间(头插法,始终让新结点在第一的位置),也可以将p插入到终端结点的后面(尾插法)

在这里插入图片描述

// 头插法,创建 n 个元素的链表
void CreateListHead (LinkList *head, int n)
{
    (*head) = (LinkList)malloc(sizeof(LNode));
    (*head)->next = NULL;

    LinkList p, t;
    p = (*head);
    int i;
    for (i = 0; i<n; i++)
    {
        ElemType elem;
        elem = inputStruct(&elem);
        t = (LinkList)malloc(sizeof(LNode));
        t->data = elem;

        t->next = p->next;
        p->next = t;
    }
}

void CreateListTail (LinkList *head, int n)
{
    (*head) = (LinkList)malloc(sizeof(LNode));
    (*head)->next = NULL;
    int i = 0;
    LinkList p, t;
    p = (*head);
    for (i = 0; i<n; i++)
    {
        t = (LinkList)malloc(sizeof(LNode));
        ElemType elem;
        t->data = inputStruct(&elem);
        t->next = NULL;

        p->next = t;
        p = t;

    }
}

单链表的销毁

单链表整表删除的算法思路如下:

声明一结点p和q
将第一个结点赋值给p

循环
    将下一结点赋值给q
    释放p
    将q赋值给p
Status DestroyLinkList (LinkList *head)
{
    LinkList p;
    p = (*head);
    LinkList t;         // t 是十分有必要的;
    while (p != NULL)
    {
        t = p;
        p = p->next;
        free(t);
    }
    *head = NULL;
    return OK;
}

两个有序链表并为一个有序链表:

· 跟顺序存储链表类似,只不过改变了链表结点之间的关联性。

算法实现://MergeList_L

void MergeList_L(LinkList &La, LinkList &Lb, LinkList &Lc) {
    //已知单链线性表La和Lb的元素按递增排列
    //求并集,得到新的单链线性表Lc,它也按递增排列
    LinkList pa, pb, pc;
    pa = La->next;
    pb = Lb->next;
    Lc = pc = La;   //用La的头结点作为Lc的头结点
    while (pa && pb) {
        if (pa->data < pb->data) {
            pc->next = pa;
            pc = pa;
            pa = pa->next;
        } else if (pa->data > pb->data) {
            pc->next = pb;
            pc = pb;
            pb = pb->next;
        } else {
            pc->next = pa;
            pc = pa;
            pa = pa->next;
            pb = pb->next;
        }
    }
    pc->next = pa ? pa : pb;  //插入剩余片段
    free(Lb);       //释放头结点
}
```c
// https://www.cnblogs.com/roverliang/p/11468815.html
//  头结点为 存取数据元素 的情况
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define TRUE    1
#define FALSE   0

#define OK      1
#define ERROR   0
#define Status  int

typedef struct{
    char  no[20];
    char  name[50];
    float price;
}Book;

typedef Book ElemType;

typedef struct LNode
{
    ElemType    data;
    struct LNode *next;
}LNode, *LinkList;          //LinkList为指向结构体LNode的指针类型

// 初始化,对头指针进行修改操作
void InitLinkList (LinkList *head)
{
    (*head) = (LinkList)malloc(sizeof(LNode));  //注意在函数中,(*head) 代表head指向的结点
    (*head)->next = NULL;
}

Status IsEmptyList (LinkList head)
{
    if (head->next == NULL)
        return OK;
    else
        return ERROR;
}

Status LenLinkList (LinkList head)
{
    int num = 0;
    LinkList p;
    p = head;
    while (p->next != NULL)
    {
        num++;
        p = p->next;
    }

    return num;
}



//释放单链表L的所有结点,但保留头结点
Status ClearList(LinkList head)
{
    LinkList p, t;
    p = head->next;
    while (p != NULL)
    {
        t = p;
        p = p->next;
        free(t);
    }
    head->next = NULL;
    return OK;
}

Status InsertSData (LinkList *head, int i, ElemType elem)
// 注:也可以使用head, 因为head指向头结点,所指向的头结点的地址为发生改变即可
{
    LinkList p;
    p = (*head);       // p 指向头结点
    int j = 0;
    while (j < (i-1) && p->next != NULL )  // j < i : j在第 i-1 的位置   // 注意 j 与 p 的指向必须一致
    {                                   // p 指向 第 j 个结点
        j++;
        p = p->next;
    }
    if (!(p->next) || j> (i-1))  // 判断是否是缺少结点, 或者 j 计数错误
    {
        printf("i is error\n");
        return ERROR;
    }
    else
    {

        LinkList temp;
        temp  = (LinkList)malloc(sizeof(LNode));
        temp->data = elem;

        temp->next = p->next;
        p->next = temp;

        return OK;
    }

/*
    if (p = NULL)
    {
        printf("LinkList is Empty");

    }// 可能插入在空表中,故不能作为依据;*/
}

// 先连接,后删除!
Status DeleteSData (LinkList *head, int i, ElemType *e)
{
    LinkList p;
    p = (*head);
    int j = 0;
    while (j < (i-1) && p->next != NULL)
    {
        j ++;
        p = p->next;
    }
    if (!(p->next) || j > (i-1))//  (j!=i-1)
    {
        printf("i is error\n");
        return ERROR;
    }
    else
    {
        if (p->next != NULL)//判断是否有第 i 个结点
        {
            LinkList temp;
            temp = p->next;
            *e = temp->data;
            p->next = p->next->next;
            free(temp);
            return OK;
        }
        else
        {
            printf("i is error, \n");
            return ERROR;
        }
    }
}




/*
void DelLinklist(Linklist *head, int data)//删除结点值 = data的结点。
{
    Linklist *p, *q;
    q = head;
    p = head->next;
    while (p->next != NULL && p->data != data)
    {
        p = p->next;
        q = q->next;
    }
    if (p->data != data && p->next == NULL)
        printf("链表中无此节点");
    else
    {
        q->next = p->next;
        free(p);
    }
}//还未实现多个结点==某个值
*/


Status GetElem (LinkList head, int i, ElemType *e)
{
    int j=0;
    LinkList p;
    p = head;
    while (j < i && p->next != NULL)
    {
        j++;
        p = p->next;
    }
    if (!p || j>i)
    {
        printf("i is error");
        return ERROR;
    }
    else
    {
        *e = p->data;
        return OK;
    }
}



void PrintSData (LinkList head)
{
    LinkList p;
    p = head;
    while (p->next != NULL)
    {
        p = p->next;//(".....\n");//
        printf ("图书编号:%s, 图书书名:%s, 图书价格:%f\n", p->data.no, p->data.name, p->data.price);
    }
}


Status DestroyLinkList (LinkList *head)
{
    LinkList p;
    p = (*head);
    LinkList t;         // t 是十分有必要的;
    while (p != NULL)   // 注意销毁时的循环条件为 头指针 不为0; 不同于其他函数 为 头结点的指针域
    {
        t = p;
        p = p->next;
        free(t);
    }
    *head = NULL;
    return OK;
}



ElemType InputSData (ElemType *p)
{
    printf("请按要求输入数据元素:\n");
    scanf("%s", &p->no);
    scanf("%s", &p->name);
    scanf("%f", &p->price);
    return *p ;
}


// 尾插法,创建 n 个元素的链表
void CreateListTail (LinkList *head, int n)
{
    (*head) = (LinkList)malloc(sizeof(LNode));
    (*head)->next = NULL;
    int i = 0;
    LinkList p, t;
    p = (*head);
    for (i = 0; i<n; i++)
    {
        t = (LinkList)malloc(sizeof(LNode));
        ElemType elem;
        t->data = InputSData(&elem);
        t->next = NULL;

        p->next = t;
        p = t;

    }
}
/* 尾插法2
LinkList *Create(LinkList *L)
{
	Node *r,*s;
	int c,n;
	int i;
	*L=(LinkList)malloc(sizeof(Node));
	r=*L;
	printf("你要输入多少个数:\n");
	scanf("%d",&n);
	for(i=1;i<=n;i++)
	{
		printf("No.%d:",i);
		scanf("%d",&c);
		s=(LinkList)malloc(sizeof(Node));
		s->data=c;
		s->next=NULL;
		r->next=s;
		r=s;
	}
	return L;
}
*/
/* 尾插法3
Linklist* InitList(int i)//i为链表大小
{
    Linklist *head;
    head = (Linklist*)malloc(sizeof(Linklist));
    Linklist *end=head;
    int j = 0;
    for (j = 0;j < i;j++)
    {
        Linklist *node = (Linklist*)malloc(sizeof(Linklist));
        scanf("%d", &node->data);
        end->next = node;
        end = node;
    }
    end->next = NULL;
    return head;
}
*/



// 头插法,创建 n 个元素的链表
void CreateListHead (LinkList *head, int n)
{
    (*head) = (LinkList)malloc(sizeof(LNode));
    (*head)->next = NULL;

    LinkList p, t;
    p = (*head);
    int i;
    for (i = 0; i<n; i++)
    {
        ElemType elem;
        elem = InputSData(&elem);
        t = (LinkList)malloc(sizeof(LNode));
        t->data = elem;

        t->next = p->next;
        p->next = t;
    }
}



int main()
{
    /*头插法验证
    LinkList L;
    CreateListHead(&L, 4);
    PrintLinkList(L);
*/
    /*尾插法验证
    LinkList L;
    CreateListTail(&L, 5);
    PrintLinkList(L);
    */
    /*普通方法验证
    LinkList head;
    InitLinkList(&head);

    ElemType elem1, elem2, elem3;

    inputStruct(&elem1);
    InsertList(&head, 1, elem1);
    inputStruct(&elem2);
    InsertList(&head, 2, elem2);

    printf("链表长度:%d\n", LenLinkList(head));
    printf("链表是否为空: %d. (空为1,非空为0)\n", IsEmptyList(head));


    ElemType t;
    GetElem(head, 1, &t);
    printf("图书编号:%s, 图书书名:%s, 图书价格:%f\n", t.no, t.name, t.price);

    GetElem(head, 2, &t);
    printf("图书编号:%s, 图书书名:%s, 图书价格:%f\n", t.no, t.name, t.price);



    PrintLinkList(head);


    printf("在链表中间(第二个位置)插入元素:\n");
    inputStruct(&elem3);
    InsertList(&head, 2, elem3);
    PrintLinkList(head);


    printf("删除表尾元素:\n");
    DeleteList(&head, 3, &t);
    PrintLinkList(head);

    printf("删除非表尾元素:\n");
    DeleteList(&head, 1, &t);
    PrintLinkList(head);

    printf("%p", head);
    DestroyLinkList(&head);
    printf(": %d. \n", DestroyLinkList(&head));
    printf("%p", head);
    */
    /* 判断去清除与销毁链表
    LinkList L;
    CreateListHead(&L, 2);
    PrintLinkList(L);
    printf("长度:%d\n", LenLinkList(L));
    ClearList(L);
    printf("长度:%d\n", LenLinkList(L));
    printf("%p\n", L);
    DestroyLinkList(&L);
    printf("%p\n", L);
*/


    LinkList L;
	int choice;
	do
	{
printf("1.ListInit\t2.CreateListHead(头插法)\t3.CreateListTail(尾插法)\n4.PrintList\t5.GetLength\t6.ListEmpty\n7.Getelem\t8.ListInsert\t9.ListDelete\n10.ClearList\t11.DestroyList\n");

		printf("\n请输入你的选择:\n");
		scanf("%d",&choice);
		switch(choice)
		{
		system("cls");
		case 0:
		    printf("byebye\n");
			break;
		case 1:
			{
			    InitLinkList(&L);
			    break;
			    /*
				if(InitLinkList(&L)==OK)  // void
                    printf("success\n");
				else
					printf("flase\n");
				break;
				*/
			}
		case 2:
			{
				int n;
				printf("请输入你要存放的数据量:\n");
				scanf("%d",&n);
				CreateListHead(&L, n);
				break;
			}
		case 3:
			{
				//L=*Create(&L);
				int n;
				printf("请输入你要存放的数据量:\n");
				scanf("%d",&n);
				CreateListTail(&L, n);
				break;
			}
		case 4:
			{
				PrintSData(L);
				printf("\n");
				break;
			}
		case 5:
			{
				int count;
				count=LenLinkList(L);
				printf("长度为:%d\n",count);
				break;
			}
		case 6:
			{
				printf("链表是否为空:%d. (1为空,0为非空)\n", IsEmptyList(L));
				break;
			}
		case 7:
			{
				int i;
				ElemType e;
				printf("请问你想要第几个数:\n");
				scanf("%d",&i);
				printf("图书编号:%s,图书书名:%s, 图书价格:%f", e.no, e.name, e.price);
				break;
			}
		case 8:
			{
				int i;
				ElemType elem;
				InputSData(&elem);

				printf("请输入你要插入的位置:\n");
				scanf("%d",&i);
				if (InsertSData(&L, i, elem) == OK)
                    printf("插入成功");
                else
                    printf("插入失败");
				break;
			}
		case 9:
			{
				int i;
				ElemType e;
				printf("你想要删除第几个元素:\n");
				scanf("%d",&i);
				if(DeleteSData(&L,i,&e) == OK)
					printf("删除成功\n");
				else
					printf("删除失败\n");
				printf("第%d个数是:\n图书编号:%s,图书书名:%s, 图书价格:%f\n", i, e.no, e.name, e.price);
                break;
			}
        case 10:
            {
                printf("........");
                if (ClearList(L) == OK)
                    printf("清除成功");
                else
                    printf("清除失败");

                printf("长度:%d\n", LenLinkList(L));
                printf("%p\n", L);
                break;
            }
        case 11:
            {
                DestroyLinkList(&L);
                printf("%p\n", L);
                break;
            }
		default:
		    printf("输入错误,请重新输入\n");
			break;
			system("pause");
		}
	}while(choice!=0);

    return 0;
}
  • 9
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何为xl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值