C数据结构单链表基本操作

上面讲了顺序表后,顺序表的缺陷是什么呢?

1.动态增容有性能消耗

2.需要头部插入数据,需要挪动数据

为什么需要单链表?

鉴于以上两点缺陷,我们引入链表。

链表的概念及结构概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

链表的逻辑结构如下:

 链表结点进行了分层,上层用于存储数据 ; 下层用指针指向下一个结点,以达到连接目的

顺序表VS单链表:

1.顺序表是利用realloc一次性动态调整出一大块空间,所以这一大块空间中的每个单元,地址都是连续的。

2.单链表每一次都是用的malloc开辟的一个空间,那么每个空间的地址一定是不一样且不连续的。

废话不多说,用代码实现一下,还是创建三个头文件:

定义单链表结构:

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDataType; //方便以后修改数据类型

struct SListNode
{
    SLTDatType data;
    struct SListNode* next;     
}SLNode; //把结构体名改短一点

思考下为何使用typedef?上次讲顺序表就说过,就不再重复了!

单链表的空间开辟:

SLNode* BuySLTNode(SLTDatType val)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
    if(newnode == NULL)
    {
    	perror("错误原因:");
        exit(-1);
    }
    newnode->data = val;
    newnode->next = NULL;    
    return newnode;
}

单链表的尾插:

完成尾插前先回忆下单链表的结构:

phead头指针)指向头结点(第一个结点),之后的每个结点的下层指针next指向下一个结点,其中尾结点的next为空.

 实现思路

1.遍历找到最后一个结点(即其next为空)

2.malloc动态开辟一个空间存储数据,然后把新开辟的空间的next置为空.

3.使用尾结点的next连接新开辟的空间

下面程序注意一下(错误):

void SListPushBack(SLNode* phead,SLTDataType val)
{

    //第一步:找尾结点,  即cur->next 等于 NULL
    SLNode* cur = phead;
    while(cur->next != NULL) //cur用于迭代
    {
        cur = cur->next;
    }
    //第二步:开辟新空间
    SLNode* newnode = BuySLTNode(val);
    //第三步:连接
    cur->next = newnode;
}

上面程序有问题:原因plist的类型为SLTNode *,而我们形参类型也是SLTNode *,这属于值传递,值传递相当于形参是实参的一份临时拷贝,形参的改变并不会影响实参的值。想要修改实参的值就需要进行传址操作,在这里传plist的地址.形参用二级指针。

还有不要忘了断言。如果phead为空,会引发异常

修改程序后:

void SListPushBack(SLNode** pphead, SLTDataType val)
{
	assert(pphead); //pphead不可以为空指针.
	if (*pphead == NULL)
	{
		*pphead = BuySLTNode(val);
	}
	else
	{
		//第一步:找尾结点,  即cur->next 等于 NULL
		SLNode* cur = *pphead;
		while (cur->next != NULL) //cur用于迭代
		{
			cur = cur->next;
		}
		//第二步:开辟新空间
		SLNode* newnode = BuySLTNode(val);
		//第三步:连接
		cur->next = newnode;
	}
}

单链表的头插:

这一点要注意:函数想要改变phead的值,所以我们的形参需要二级指针。

创建新节点:

 新节点链接原来的头节点:

 让phead指针指向新节点:

 

void SListPushFront(SLNode** pphead,SLTDataType val)
{
    assert(pphead);
    //第一步,创建新节点
    SLNode* newnode = BuySLTNode(val);
    //第二步,新结点链接原来的头结点
    newnode->next = *pphead;
    //第三步,phead指针指向新节点
    *pphead = newnode;
}

单链表的尾删:

        1.考虑正常情况,如下图。

        2.考虑结点数量只有0或1时。

从头开始遍历,找到倒数第二个节点:

 然后free掉最后一个节点:

 最后将倒数第二个节点的next置NULL:

 

void SListPopBack(SLNode** pphead)
{
    assert(pphead);  //结点数0
    assert(*pphead);  //结点数1
    
    if((*pphead)->next == NULL)//只有一个结点时
    {
        free(*pphead);
        *pphead = NULL;
        return;
    }

	//多节点,第一步,找倒数第二个结点
	SLNode* cur = *pphead;
	while (cur->next->next != NULL) 
	{
		cur = cur->next;
	}
	//第二步,free掉最后一个结点
	free(cur->next);
	//第三步,将现结点释放掉,置NULL
	cur->next = NULL;
}

单链表的头删:

1.先把第二个结点的地址记下来。

2.释放第一个结点。

3.phead链接到原来的第二个结点

void SListPopFront(SLNode** pphead)
{
	assert(pphead);
	//0结点
	assert(*pphead);
	//1结点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
		return;
	}
    
	//多结点,第一步,保留第二个结点地址
	SLNode* next = (*pphead)->next;
	//第二步,释放第一个结点
	free(*pphead);
	//第三步,连接第二个
	*pphead = next;
}

单链表的长度查找:

这个函数没有修改phead,所以选择值传递,不改变实参。

int SListSize(SLNode* phead)
{
    SLTNode* cur = phead;
    int size = 0;
    while(cur->next != NULL)
    {
        size++;
        cur = cur->next;
    };
    return size;
}

单链表判空操作:

bool SListEmpty(SLNode* phead)
{
	return phead == NULL;
}

单链表的值查找操作:

        如果可以找到,就返回那个结点,如果找不到,返回空指针。

SLNode* SListFind(SLNode* phead, SLTDataType val)
{
    SLTNode* cur = phead;
    while(cur)
    {
        if(cur->data==val)
        {
            return cur;
        }
        cur = cur->next;
    }
    return NULL;
}

单链表的数据打印:

void SListPrint(SLNode* phead)
{
    SLTNode* cur = phead;
    while(cur->next != NULL)
    {
        printf("%d->",cur->data);
    }
    printf("NULL\n");
}

单链表的删除操作:

1.找到目标结点之前位置

2.提前保存目标结点后位置

3.销毁目标结点

4.链接原目标结点之前的位置与原目标结点之后的位置

void SListErase(SLNode** pphead,SLNode* pos)
{
    assert(pphead);
    //0结点情况
    assert(*pphead);
    //1结点时.相当于头删,直接调用头删.
    if((*pphead)->next == NULL)
    {
        SListPopFront(pphead);
    }
    else
    {
        SLTNode* cur = *pphead;
        while(cur->next != pos)
        {
            cur = cur->next;
        }
        SLNode* next = pos->next;
        free(pos);
        cur->next = next;
    }
}

单链表的插入操作:

1.找到目标结点之前结点

2.创建新结点

3.新结点链接目标结点

4.原目标结点之前的结点链接新结点

void SListInsert(SLNode** pphead,SLNode* pos,SLTDataType val)
{
	assert(pphead);
	assert(pos);
    //当第一个结点便是目标结点,其实就是头查
	if (*pphead== pos)
	{
		SListPushFront(pphead,elem);
	}
    //当多个结点时
	else
	{
		SLNode* cur = *pphead;
		while (cur->next  != pos)
		{
			cur = cur->next;
		}
		SLNode* next = BuySLTNode(val);//创建准备插入的结点
		next->next = pos;
		cur->next = next;
	}
}

单链表的销毁操作:

void SListDestory(SLNode** pphead)
{
	assert(pphead);

	SLNode* cur = *pphead;
	while (cur)
	{
		SLNode* next = cur->next;
		free(cur);

		cur = next;
	}

	*pphead = NULL;
}

数据结构的单链表内容到此设计结束了,感谢您的阅读!!!

如果内容对你有帮助的话,记得给我三连(点赞、收藏、关注)——做个手有余香的人。感谢大家的支持!!!

数据结构中的单链表是一种线性的数据结构,每个节点包含两个部分:数据域和指针域。在C语言中,我们可以这样实现和操作单链表: 1. **创建链表**: - 定义链表节点结构体 `struct Node` 包含整型数据 `data` 和指向下一个节点的指针 `next`。 ```c struct Node { int data; struct Node* next; }; ``` 2. **初始化链表**: - 初始化一个空链表可以设置头指针为 `NULL`。 ```c struct Node* head = NULL; ``` 3. **插入节点**: - 可以通过节点的指针逐个添加新节点,如在链表头部、尾部或指定位置插入。 ```c void insertAtStart(struct Node** head, int value) { struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); newNode->data = value; newNode->next = *head; *head = newNode; } ``` 4. **删除节点**: - 删除指定节点需要考虑特殊情况,如删除头节点、普通节点和查找特定值后删除。 ```c void deleteNode(struct Node** head, int value) { struct Node* temp = *head, *prev = NULL; if (temp != NULL && temp->data == value) { *head = temp->next; free(temp); return; } while (temp != NULL && temp->data != value) { prev = temp; temp = temp->next; } if (temp == NULL) return; // 没找到要删除的节点 prev->next = temp->next; free(temp); } ``` 5. **遍历链表**: - 使用循环结构访问每个节点的数据。 ```c void printList(struct Node* head) { while (head != NULL) { printf("%d ", head->data); head = head->next; } printf("\n"); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值