数据结构之链表

顺序表是数据结构的一种,它可以存储整型等内置类型和自定义类型
通讯录就是用顺序表的自定义类型实现的
顺序表经典算法题:
移除元素
解题思路:
在这里插入图片描述

//这里的函数返回值是指返回不为val的值的个数
//numSize是指数组元素的个数
int removeElement(int* nums, int numsSize, int val) {
    int src = 0;
    int dst = 0;
    //int src, dst;
    //src = dst = 0;
    while(src < numsSize) 
    //src不能等于numsSize:src是下标,numsSize 是有效数据个数,二者相差1
    {
        if (nums[src] == val)//记得 == 才是相等
        {
            src++;
        }
        else
        {
            nums[dst] = nums[src];
            src++;
            dst++;
        }
    }
    return dst;
}

合并两个有序数组

解题思路:
在这里插入图片描述

代码如下:

//nums1Size是指nums1的数组长度
//nums2Size是指nums2的数组长度
//上面这两个参数用不上
//这里的函数的返回值为空,原因是新的数组就在nums1中形成
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n) {
    int l1 = m - 1;
    int l2 = n - 1;
    int l3 = m + n - 1;

    while(l1 >= 0 && l2 >= 0)//只有两个同时大于等于0 才能 够说明这两个数组中的11 和 12 是有效的,才可以比较,只要有一个为假,就不能比较,要跳出循环
    {
        if (nums1[l1] >= nums2[l2])
        {
            nums1[l3--] =nums1[l1--];
        }
        else{
              nums1[l3--] =nums2[l2--];
        }
    }
    //出了循环有两种情况:l1 >= 0 或者 l2 >= 0
    //只需要关注l2 >= 0 的情况;l1 >= 0 时nums1就已经时有序数组了
    while(l2 >= 0)
    {
          nums1[l3--] =nums2[l2--];
    }
    //此时nums1中包含了nums2中的数据,并且num1是升序数组
}

//注意nums1中对应的是l1和l3,nums2对应的是l2;写代码时不要将对应的位置搞混了

针对顺序表:在中间/头部插入数据导致效率低下、增容降低运行效率、增容造成的空间浪费(100个空间不够用,数据的有效字节数为105,二倍申请到200个字节,浪费了95个空间)
解决办法:链表
链表在物理结构上不是线性的;

在逻辑结构上一定是线性的,例如:在堆上的存储看起来是混乱的,但它是用节点中的地址链接起来,将他拉成一条直线就是线性的

链表也是线性表的一种;线性表在物理结构上不一定是线性的
例:

int a = 0;
float b = 0;
//在内存中存储a和b的空间不一定是连续的(线性的)

在逻辑结构上一定是线性的

链表是是由一个一个(节点)结点组成,就像火车是由一节一节车厢组成的

在这里插入图片描述

链表的好处:可以任意删除一个节点,再将其它节点进行连接起来,节点是相互独立的;数组在删除时不能直接将数组中某个元素删除
节点是有什么组成的呢?
节点是由 数据 和 指向下一个节点的指针 构成的
那我们现在如何定义链表呢? 定义链表就是定义节点的结构,可以使用结构体完成对链表的定义

struct SListNode//S 是指singl - 单链表  Node - 节点
{
	int data;//数据
	struct SListNode * next;//指向下一个节点的指针,它的数据类型还是结构体指针
}

以下是链表的代码:
代码对应的思路在这里
SList.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

//定义节点的结构
//数据 + 指向下一个节点的指针
typedef int SLDataType;
typedef struct SLTList
{
	SLDataType data;
	struct SLTNode* next;//
	//SLT* next;//注意这里的结构体中的成员不得使用重命名后的名字
}SLT;

//链表打印
void SLTPrint(SLT* phead);
SLT* SLTBuyNode(SLDataType x);

//尾插
void SLTPushBack(SLT** pphead, SLDataType x);//不需要返回值

//头插

void SLTPushFrant(SLT** pphead, SLDataType x);

//尾删
void SLTPopBack(SLT** pphead);


SList.c

#define _CRT_SECURE_NO_WARNINGS 
#include"SList.h"

void SLTPrint(SLT* phead)
{
	SLT* pcur = phead;
	while (pcur)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

SLT* SLTBuyNode(SLDataType x)
{
	SLT* newnode = (SLT*)malloc(sizeof(SLT));
		if (newnode == NULL)//易错点
		{
			perror("SLTBuynode fail");
			exit(1);//记得带上() --- return 1;
		}
		newnode->data = x;
		newnode->next = NULL;

		return newnode;

}

void SLTPushBack(SLT** pphead, SLDataType x)
{
	assert(pphead);//在使用*pphead 时不能对空指针进行解引用
	//*pphead 可以为空,代表着没有节点,时空链表
	SLT* newnode = SLTBuyNode(x);//这里已经初始化过了不需要加上类型
	//空链表
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLT* pcur = *pphead;
		while (pcur->next)//是它的下一个地址为空才能找出尾节点
		{
			pcur= pcur->next;
		}

		pcur->next = newnode;

	}
	

}

//头插的空链表和非空链表是通用的
void SLTPushFrant(SLT** pphead, SLDataType x)
{
	assert(pphead);
	SLT* newnode = SLTBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

//尾删
void SLTPopBack(SLT** pphead)
{
	assert(pphead && *pphead);
		//pphead - 表示传参时不能传空指针,*pphead无法解引用
		//不能是空链表,*pphead为空时,代表该链表是空链表
	//只有一个节点时
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//多个节点时
	else
	{
		//先找未节点和前一个节点,将未节点进行释放,前一个节点中指向下一个节点的地址置为空
		SLT* prve = *pphead;
		SLT* ptail = *pphead;
		while (ptail->next)
		{
			prve = ptail;//这里是倒数第二次循环时找到尾节点中的前一个节点
			ptail = ptail->next;//这里是尾节点
		}
		free(ptail);
		ptail = NULL;
		prve->next = NULL;

	}
}

Test.c

#define _CRT_SECURE_NO_WARNINGS 
#include"SList.h"

void SLTest01()
{
	//创建节点 - 这里是为了测试打印节点用的,在正规中我们使用插入的形式进行创建节点
	SLT* node1 = (SLT*)malloc(sizeof(SLT));
	node1->data = 1;

	SLT* node2 = (SLT*)malloc(sizeof(SLT));//使用
	node2->data = 2;

	SLT* node3 = (SLT*)malloc(sizeof(SLT));
	node3->data = 3;

	SLT* node4= (SLT*)malloc(sizeof(SLT));
	node4->data =4;

	//连接节点
	node1->next = node2;
	node2->next = node3;
	node3->next = node4;
	node4->next = NULL;

	//创建一个新的节点进行打印, 为啥要创建新的节点:为了后续要再使用node1 时方便回到原来的位置
	SLT* plist = node1;
	SLTPrint(plist);
}

void SLTest02()
{
	 SLT* plist = NULL;
	 SLTPushBack(&plist, 5);//别忘了取地址	
	 SLTPrint(plist);
	 SLTPushBack(&plist, 6);
	 SLTPrint(plist);
	 SLTPushBack(&plist, 7);
	 SLTPrint(plist);

	 //假设前面的是非空链表
	 SLTPushBack(&plist, 8);
	 SLTPrint(plist);
	 //SLTPushBack(NULL, 8);//err
	 //SLTPrint(plist);

	 SLTPushFrant(&plist, 1);
	 SLTPrint(plist);
	 SLTPushFrant(&plist, 2);
	 SLTPrint(plist);
	 SLTPushFrant(&plist, 3);
	 SLTPrint(plist);
	 SLTPushFrant(&plist, 4);
	 SLTPrint(plist);
	 ////SLTPushFrant(NULL, 1);err
	 ////SLTPrint(plist);

	 //测试尾删
	 SLTPopBack(&plist);
	 SLTPrint(plist);
	 SLTPopBack(&plist);
	 SLTPrint(plist);
	 SLTPopBack(&plist);
	 SLTPrint(plist);
	 SLTPopBack(&plist);
	 SLTPrint(plist);

	// SLTPopBack(&plist);//err -测试删除空链表
	// SLTPrint(plist);
}
//上面的02既可以检测空链表也可以检测非空链表
int main()
{
	SLTest01();
	SLTest02();
	return 0;
}

单链表经典算法题:
移除链表元素

反转链表

链表的中间结点

合并两个有序链表

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值