数据结构:单链表

1、链表的概念和结构

链表是物理存储结构上非连续,非顺序的存储结构,逻辑顺序是通过链表指针实现的。

链式结构在逻辑上是连续的,在物理上是不一定连续的。

链表的结构像火车一样,一节一节的,每节车厢都是独立存在的。

 单链表结构:

struct SListNode
{
    int data;//节点数据
    struct SListNode*next;//存放下一个数据的地址(节点)
};

节点是从堆上申请空间的,从堆上申请的空间可能连续,可能不连续。

2、单链表的实现

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

typedef int SLTDataType;

typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;
//打印
void SLTPrint(SLTNode*ps);

//尾插
void SLTPushBack(SLTNode**pps, SLTDataType x);
//头插
void SLTPushFront(SLTNode** pps, SLTDataType x);
//尾删
void SLTPopBack(SLTNode** pps);
//头删
void SLTPopFront(SLTNode**pps);
//查找
SLTNode* SLTFind(SLTNode* ps, SLTDataType x);
//指定位置之前插入数据
void SLTInsert(SLTNode** pps, SLTNode* pos, SLTDataType x);
//指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pps, SLTNode* pos);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SLTDestroy(SLTNode** pps);
#include"SList.h"
//打印
void SLTPrint(SLTNode* ps)
{
	assert(ps);
	SLTNode* pcur = ps;
	while (pcur)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}
SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;//动态内存只能用free释放,函数结束后并不会自动销毁
}
//尾插
void SLTPushBack(SLTNode** pps, SLTDataType x)
{
	assert(pps);
	SLTNode* newnode = SLTBuyNode(x);
	if (*pps == NULL)
	{
		*pps = newnode;
	}
	else
	{
		//找尾
		SLTNode* ptail = *pps;
		while (ptail->next)
		{
			ptail = ptail->next;
		}
		ptail->next = newnode;
	}
}
//头插
void SLTPushFront(SLTNode** pps, SLTDataType x)
{
	assert(pps);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pps;
	*pps = newnode;
}
//尾删
void SLTPopBack(SLTNode** pps)
{
	assert(pps&&*pps);
	if ((*pps)->next == NULL)//->优先级比*高
	{
		free(*pps);
		*pps = NULL;
	}
	else
	{
		SLTNode* prev = *pps;
		SLTNode* ptail = *pps;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		free(ptail);
		ptail = NULL;
		prev->next = NULL;
	}
}
//头删
void SLTPopFront(SLTNode** pps)
{
	assert(pps && *pps);
	if ((*pps)->next == NULL)
	{
		free(*pps);
		*pps = NULL;
	}
	else
	{
		SLTNode* pcur = (*pps)->next;
		free(*pps);
		*pps = NULL;
		*pps = pcur;
	}
}
//查找
SLTNode* SLTFind(SLTNode* ps, SLTDataType x)
{
	assert(ps);
	SLTNode* pcur = ps;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
//指定位置之前插入数据
void SLTInsert(SLTNode** pps, SLTNode* pos, SLTDataType x)
{
	assert(pps&&*pps);
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	if (pos == *pps)
	{
		SLTPushFront(pps, x);
	}
	else
	{
		SLTNode* prev = *pps;
		while (prev->next != *pps)
		{
			prev = prev->next;
		}
		newnode->next = pos;
		prev->next = newnode;
	}
}
//指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	SLTNode* prev = pos->next;
	pos->next = newnode;
	newnode->next = prev;
}
//删除pos节点
void SLTErase(SLTNode** pps, SLTNode* pos)
{
	assert(pps && *pps);
	assert(pos);
	if (pos == *pps)
	{
		SLTPopFront(pps);
	}
	else
	{
		SLTNode* prev = *pps;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);
	SLTNode* prev = pos->next;
	pos->next = prev->next;
	free(prev);
	prev = NULL;
}
//销毁链表
void SLTDestroy(SLTNode** pps)
{
	assert(pps&&*pps);
	SLTNode* pcur=*pps;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pps = NULL;
}

3、链表的分类

链表的结构有很多,有8种(2*2*2)链表结构:

分别为  带头/不带头  单向/双向  循环/不循环

带头就是链表一开始就有一个节点

双向就是节点可以指向下一个的节点以及上一个的节点(首尾节点除外)

循环就是尾节点的下一个节点指向首节点

链表种类有很多,但我们平常只使用无头单向非循环链表(单链表)和有头双向循环链表

4、单链表经典算法

反转链表:

typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
    if(head==NULL)
    {
        return NULL;
    }
    ListNode*n1=NULL;
    ListNode*n2=head;
    ListNode*n3=head->next;
    while(n2)
    {
        n2->next=n1;
        n1=n2;
        n2=n3;
        if(n3)
            n3=n3->next;
    }
    return n1;
}
移除链表元素: 

typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {
    ListNode*newnode=NULL;
    ListNode*tailnode=NULL;
    ListNode*pcur=head;
    while(pcur)
    {
        if(pcur->val!=val)
        {
            if(newnode==NULL)
            {
                newnode=tailnode=pcur;
            }
            else
            {
                tailnode->next=pcur;
                tailnode=tailnode->next;
            }
        }
        pcur=pcur->next;
    }
    if(newnode)
        tailnode->next=NULL;
    return newnode;
}
链表的中间节点:

 typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
    if(head==NULL)
    {
        return NULL;
    }
    ListNode*slow=head;
    ListNode*fast=head;
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
    }
    return slow;
}
 环形链表的约瑟夫问题:

typedef struct ListNode ListNode;
 ListNode*buyNode(int x)
 {
    ListNode*newnode=(ListNode*)malloc(sizeof(ListNode));
    newnode->val=x;
    newnode->next=NULL;
    return newnode;
 }
 ListNode* createCircle(int n)
 {
    ListNode*phead=buyNode(1);
    ListNode*ptail=phead;
    for(int i=2;i<=n;i++)
    {
        ptail->next=buyNode(i);
        ptail=ptail->next;
    }
    ptail->next=phead;
    return ptail;
 }
int ysf(int n, int m ) {
    ListNode*prev=createCircle(n);
    ListNode*pcur=prev->next;
    int count=1;
    while(prev->next!=prev)
    {
        if(count==m)
        {
            prev->next=pcur->next;
            free(pcur);
            pcur=prev->next;
            count=1;
        }
        else 
        {
            prev=pcur;
            pcur=pcur->next;
            count++;
        }
    }
    return prev->val;
}
 合并两个有序链表:

typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    if(list1==NULL)
    {
        return list2;
    }
    if(list2==NULL)
    {
        return list1;
    }
    ListNode*newhead=NULL;
    ListNode*newtail=NULL;
    ListNode*p1=list1;
    ListNode*p2=list2;
    while(p1&&p2)
    {
        if(p1->val<=p2->val)
        {
            if(newhead==NULL)
            {
                newhead=newtail=p1;
            }
            else
            {
                newtail->next=p1;
                newtail=newtail->next;
            }
            p1=p1->next;
        }
        else
        {
            if(newhead==NULL)
            {
                newhead=newtail=p2;
            }
            else
            {
                newtail->next=p2;
                newtail=newtail->next;
            }
            p2=p2->next;
        }
    }
    if(p1==NULL)
    {
        while(p2)
        {
            newtail->next=p2;
            newtail=newtail->next;
            p2=p2->next;
        }
    }
    if(p2==NULL)
    {
        while(p1)
        {
            newtail->next=p1;
            newtail=newtail->next;
            p1=p1->next;
        }
    }
    return newhead;
}

 5、双链表的实现

之前说双链表就是带头双向循环链表,带头与头结点不一样,这里的头结点为“哨兵位”,哨兵位节点不存储任何有效元素,只是用来放哨的,存在的意义就是防止遍历循环链表时出现死循环。

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

typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}LTNode;

//初始化
void LTInit(LTNode** pps);
//销毁
void LTDestroy(LTNode* ps);
//链表打印
void LTPrint(LTNode* ps);
//尾插
void LTPushBack(LTNode* ps, LTDataType x);
//头插
void LTPushFront(LTNode* ps, LTDataType x);
//尾删
void LTPopBack(LTNode* ps);
//头删
void LTPopFront(LTNode* ps);
//查找
LTNode* LTFind(LTNode* ps, LTDataType x);
//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);
//删除pos节点
void LTErase(LTNode* pos);
#include"List.h"

//初始化
LTNode* LTBuyNode(LTDataType x)
{
	//申请节点
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(0);
	}
	newnode->data = x;
	newnode->next = newnode->prev = newnode;
	return newnode;
}
void LTInit(LTNode** pps)
{
	//创建一个哨兵位
	*pps = LTBuyNode(-1);
}
//销毁
void LTDestroy(LTNode* ps)
{
	assert(ps);
	LTNode* pcur = ps->next;
	while (pcur != ps)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(ps);
	ps = NULL;
}
//链表打印
void LTPrint(LTNode* ps)
{
	assert(ps);
	LTNode* pcur = ps->next;
	while (pcur != ps)
	{
		printf("%d->", pcur->data);
		pcur=pcur->next;
	}
	printf("\n");
}
//尾插,这里传一级是为了不改变哨兵位的地址
void LTPushBack(LTNode* ps, LTDataType x)//传二级也行,但不能修改哨兵位的地址
{
	assert(ps);
	LTNode* newnode = LTBuyNode(x);
	newnode->prev = ps->prev;
	newnode->next = ps;
	ps->prev->next=newnode;
	ps->prev = newnode;
}
//头插
void LTPushFront(LTNode* ps, LTDataType x)
{
	assert(ps);
	LTNode* newnode = LTBuyNode(x);
	newnode->next = ps->next;
	newnode->prev = ps;
	ps->next->prev = newnode;
	ps->next = newnode;
}
//尾删
void LTPopBack(LTNode* ps)
{
	assert(ps&&ps->next!=ps);
	LTNode* del = ps->prev;
	del->prev->next = ps;
	ps->prev = del->prev;
	free(del);
	del = NULL;
}
//头删
void LTPopFront(LTNode* ps)
{
	assert(ps && ps->next != ps);
	LTNode* del = ps->next;
	ps->next = del->next;
	del->next->prev = ps;
	free(del);
	del = NULL;
}
//查找
LTNode* LTFind(LTNode* ps, LTDataType x)
{
	assert(ps);
	LTNode* pcur = ps->next;
	while (pcur != ps)
	{
		if (pcur->data = x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);
	newnode->next = pos->next;
	newnode->prev = pos;
	pos->next->prev = newnode;
	pos->next = newnode;
}
//删除pos节点
void LTErase(LTNode* pos)
{
	assert(pos);
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;
	free(pos);
	pos = NULL;
}

 顺序表和链表的优缺点:

不同点顺序表链表
存储空间上物理上一定连续逻辑上一定连续,物理上不一定连续
随机访问支持O(1)不支持O(n)
任意位置插入/删除元素效率低O(n)只需要修改指针方向
插入动态顺序表空间不够时,需要扩容没有容量的概念
应用场景元素高效存储+频繁访问任意位置插入/删除频繁

 谢谢大家观看喽!

 

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值