数据结构基础之链表

链表

typedef int ELEM_TYPE;
//单链表有效节点的的结构体设计
typedef struct Node
{
	//数据域
	ELEM_TYPE data;
	//指针域
	struct Node* next;
}Node, * PNode;

1.单链表

链表由一系列不再内存中相连的组织构成,每个节点均包含有数据元素和指向下一个节点的指针,即next指针,最后一个节点的next指向NULL,如图

在这里插入图片描述

1.单链表的初始化

把接收到的节点head的指针域置空,这是头节点

void Init_List(struct Node* head)
{
    assert(NULL != head);
    if(NULL == head) exit(EXIT_FAILURE);
    head->next = NULL;
}
2.插入数据
//头插法
bool Insert_head(struct Node* head,ELEM_TYPE val)
{
    assert(NULL != head);
    if(NULL == head) exit(EXIT_FAILURE);
    //1.malloc新节点
    Node* pnewnode = (Node*)malloc(sizeof(Node));
    //判断申请是否成功
    if(NULL == pnewnode) exit(EXIT_FAILURE);
    pnewnode->data = val;/给这个新节点赋值,即要插入的值
    //2.插入
    pnewnode->next = head->next;
    head->next = pnewnode;
    return true;
}

//尾插法
bool Insert_tail(struct Node* head,ELEM_TYPE val)
{
    assert(NULL != head);
    if(NULL == head) exit(EXIT_FAILURE);
    //1.malloc新节点
    Node* pnewnode = (Node*)malloc(sizeof(Node));
    //判断申请是否成功
    if(NULL == pnewnode) exit(EXIT_FAILURE);
    pnewnode->data = val;
    //2.找到合适的插入位置
    Node* p head;
    while(p->next != NULL)
        p = p->next;
    //3.插入
	pnewnode->next = p->next;
    p->next = pnewnode;
    return true;
}

//按位置插
//插入到第pos个元素的后面
bool Insert_pos(struct Node* head,ELEM_TYPE val,int pos)
{
    assert(NULL != head);
    if(NULL == head) exit(EXIT_FAILURE);
    //1.malloc新节点
    Node* pnewnode = (Node*)malloc(sizeof(Node));
    //判断申请是否成功
    if(NULL == pnewnode) exit(EXIT_FAILURE);
    pnewnode->data = val;
    //2.找到合适的位置
	Node* p = head;
    for(int i = 0 ; i < pos ; i++)//这里没有=
    {
        p = p->next;
    }
    
    pnewnode->next = p->next;
    p->next = pnewnode;
    return true;
}
3.删除数据
//头删
bool Del_head(struct Node* head)
{
    assert(NULL != head);
    if(NULL == head) exit(EXIT_FAILURE);
    //判断当前链表是否为空
	if(Is_Empty(head)) return false;
    //直接跨越待删除节点 然后释放内存
    Node* p = head->next;
    head->next = p->next;
    free(p);
    p = NULL;//避免悬空指针的出现
    return true;
}

//尾删
bool Del_tail(struct Node* head)
{
    assert(NULL != head);
    if(NULL == head) exit(EXIT_FAILURE);
    //判空
    if(Is_Empty(head)) return false;
    //申请临时指针p,让其指向待删除节点的前驱
    Node* p = head;
    while(p->next->next != NULL)
    {
        p = p->next;
    }
    //申请临时指针q,让其指向待删除节点
    Node* q = p->next;
    //跨越指向+释放内存
    p->next = q->next;
    free(q);
    q = NULL;
    return true;
}

//按位置删
bool Del_pos(struct Node* head,int pos)
{
    //这里的pos类似于数组下标,第一个元素的pos = 0
    assert(NULL != head);
    assert(pos >= 0 && pos < Get_Length(head));
    if(NULL == head) exit(EXIT_FAILURE);
    //申请两个临时指针p和q,让其分别指向待删除节点的前驱和待删除节点
    Node* p = head;
    Node* q = head;
    for(int i = 0 ; i < pos ; i++)
        p = p->next;
    q = p->next;
    //跨越指向+释放内存
    p->next = q->next;
    free(q) ;
    q = NULL;
    return true;
}

//按值删(删除val第一次出现的节点)
bool Del_val(struct Node* head,ELEM_TYPE val)
{
    assert(NULL != head);
    if(NULL == head) exit(EXIT_FAILURE);
    //判空
    if(Is_Empty(head)) return false;
    //调用Search函数,查找当前值val是否存在,并用临时指针q接收
    Node* q = Search(head,val);
    if(q == NULL) return false;
    //申请临时指针p,指向q的前驱
    Node* p head;
    while(p->next != q)
        p = p->next;
    //跨越指向+释放
    p->next = q->next;
    free(q);
    q = NULL;
    return true;
}

//按值删(删除所有val值)
bool Del_all_val(struct Node* head,ELEM_TYPE val)
{
    assert(NULL != head);
    if(NULL == head) exit(EXIT_FAILURE);
    if(Is_Empty(head)) return false;
    //初始化双指针
    Node* prev = head;//前驱指针
    Node* cur = head->next;//当前指针
    //遍历链表
    while(cur != NULL)
    {
        if(cur->data == val)
        {
            prev->next = cur->next;
            free(cur);
            cur = prev->next;
        }
        else
        {
            prev = cur;
            cur = cur->next;
        }
    }
    return true;
}
4.查找
struct Node* Search(struct Node* head,ELEM_TYPE val)
{
    assert(NULL != head);
    if(NULL == head) exit(EXIT_FAILURE);
    //判空
    if(Is_Empty(head)); return NULL;
    //遍历链表
    for(Node* p = head->next; p != NULL; p = p->next)
    {
        if(p->data == val) return p;//找到了,停止循环
	}
    //没找到
    return NULL;
}
5.销毁
void Destroy(struct Node* head)
{
    //方法一:只要不是空链表,则一直头删
    /*
    while(!Is_Empty(head)) Del_head(head);
    */
    
    //方法二:申请辅助节点p,q,一直跨越节点释放
    assert(NULL != head);
    //申请指针p,让其保存头节点的下一个节点
    Node* p = head->next;
    Node* q;
    while(p != NULL)
    {
        //1.q指向p的下一个节点
        q = p->next;
        //2.释放p指向的节点
        free(p);
        //3.让p指向q指向的节点
        p = q;
    }
    //全部销毁完毕后,将头节点的指针域置空
    head->next = NULL;
}
6.判空和获取有效长度
//判空
bool Is_Empty(struct Node* head)
{
    return (head->next == NULL);
}

//获取有效值长度
int Get_Length(struct Node* head)
{
    int count = 0;
    for(Node* p = head->next; p != NULL; p = p->next)
    {
        count++;
    }
    return count;
}

2.循环链表

在单链表的基础上,让尾节点的next域指向头节点
在这里插入图片描述

//循环链表结构体设计
typedef int ELEM_TYPE;
typedef struct CNode
{
	ELEM_TYPE data;
	struct CNode* next;
}CNode, * PCNode;
1.初始化
void Init_CList(CNode* head)
{
    assert(NULL != head);
    if(NULL == head) exit(EXIT_FAILURE);
    head->next = head;//尾节点的next域指向头节点
}
2.插入
//头插法
bool Insert_head(CNode* head,ELEM_TYPE val)
{
    assert(NULL != plist);
	if (NULL == plist)
		exit(EXIT_FAILURE);
    //1.申请新节点
    CNode* pnewnode = (CNode*)malloc(sizeof(CNode));
    if(NULL == pnewnode) exit(EXIT_FAILURE);
    pnewnode->data = val;
    //2.插入
    pnewnode->next = head->next;//让尾指向头
    head->next = pnewnode;
    return true;
}

//尾插法
bool Insert_tail(CNode* head,ELEM_TYPE val)
{
    assert(NULL != plist);
	if (NULL == plist)
		exit(EXIT_FAILURE);
    //1.申请新节点
    CNode* pnewnode = (CNode*)malloc(sizeof(CNode));
    if(pnewnode == NULL) exit(EXIT_FAILURE);
    pnewnode->data = val;
    //2.找到合适的插入位置
    CNode* p =head;
    while(p->next != head)//这里不再是p->next != NULL
    {
        p = p->next;
    }
    //3.插入
	pnewnode->next = p->next;
    p->next = pnewnode;
    return true;
}

//按位置插入
bool Insert_pos(CNode* head,ELEM_TYPE val,int pos)
{
    assert(NULL != plist);
	if (NULL == plist)
		exit(EXIT_FAILURE);
    //1.申请新节点
    CNode* pnewnode = (CNode*)malloc(sizeof(CNode));
    if(pnewnode == NULL) exit(1);//等同于exit(EXIT_FAILURE)
    pnewnode->data = val;
    //2.找到合适位置
    CNode* p = head;
    for(int i = 0 ; i < pos ; i++)
    {
        p = p->next;
    }
    //3.插入
    pnewnode->next = p->next;
    p->next = pnewnode;
    
    
}
3.删除
//头删
bool Del_head(CNode* head,ELEM_TYPE val)
{
    assert(NULL != head);
    if(NULL == head) exit(1);
    //判空
    if(Is_Empty(head)) return false;
    //跨越待删除节点
    CNode* p = head->next;
    head->next = p->next;
    free(p);
    p = NULL;
    return true;
}

//尾删
bool Del_tail(CNode* head,ELEM_TYPE val)
{
    assert(NULL != head);
    if(NULL == head) exit(1);
    //判空
    if(Is_Empty(head)) return false;
    //1.申请临时指针p,让其指向待删除节点前驱
    CNode* p = head;
    while(p->next->next != head)
        p = p->next;
    //2.申请临时指针q,指向待删除节点
    CNode* q = p->next;
    //3.跨越指向+释放内存
    p->next = q->next;
    free(q);
    q = NULL;
    return true;
	
}

//按位置删
bool Del_pos(CNode* head,ELEM_TYPE,int pos)
{
    //这里的pos类似于数组下标,第一个元素的pos = 0
    assert(NULL != plist);
	assert(pos >= 0 && pos < Get_Length(plist));
	if (NULL == plist)
		exit(EXIT_FAILURE);
    //1.申请两个临时指针p和q,让其分别指向待删除节点的前驱和待删除节点
    CNode* p = head;
    CNode* q = head;
    for(int i = 0; i < pos; i++)
        p = p->next;
    q = p->next;
    //2.跨越待删除节点+释放内存
    p->next = q->next;
    free(q);
    q = NULL;
    return true;
}

//按值删(删除第一次出现val的节点)
bool Del_val(CNode* head,ELEM_TYPE val)
{
    assert(NULL != plist);
	if (NULL == plist)
		exit(EXIT_FAILURE);
    //判空
    if(Is_Empty(head)) return false;
    //1.调用Search函数,查找当前值val是否存在,用临时指针q接收
    CNode* q = Search(head,val);
    if(q == NULL) return false;
    //2.申请临时指针p,让其指向q的前驱
	CNode* p = head;
    while(p->next != q)
        p = p->next;
    //3.跨越指向+释放内存
    p->next = q->next;
    free(q);
    q = NULL;
    return true;
}

//按值删(删除所有val值节点)
bool Del_all_val(CNode* head,ELEM_TYPE val)
{
    assert(NULL != plist);
	if (NULL == plist)
		exit(EXIT_FAILURE);
    //判空
    if(Is_Empty(head)) return false;
    CNode* p = head;
    CNode* q = head;
    //遍历链表
    //指针p和q分别指向val的前驱和val(如果有),方便跨越指向待删除节点
    while(p->next != head)
    {
        q = p->next;
        if(q->data == val)//找到了,跨越待删除节点并释放
        {
            p->next = q->next;
            free(q);
            q = NULL;
        }
        else p = q;//没找到,p,q都往前走一步
    }
    return true;
}
4.查找
CNode* Search(CNode* head, ELEM_TYPE val)
{
	for (CNode* p = head->next; p != head; p = p->next)
	{
		if (p->data == val)
			return p;
	}
	return NULL;
}
5.销毁
void Destroy(CNode* head)
{
    assert(NULL != head);
    //1.申请临时指针p,让其保存头节点的指针域,也就是第一个有效节点
    CNode* p = head->next;
    //2.申请临时指针q
    CNode* q;
    //3.遍历节点
	while(p != head)
    {
        //1.q指向p的下一个节点
        q = p->next;
        //2.释放p
        free(p);
        //3.让p指向q
        p = q;
    }
    //4.节点全部销毁完毕,别忘了把辅助节点的指针域置空
	head->next = NULL;
}
6.判空+获取有效值长度
//判空
bool Is_Empty(CNode* head)
{
	return head == head->next;
}

//获取有效值长度
int Get_Length(CNode* head)
{
	int count = 0;
	for (CNode* p = head->next; p != h; p = p->next)
	{
		count++;
	}
	return count;
}

3.双向链表

在单链表的基础上,多加上一个prior域,让其指向自己的前驱,这样,每个节点既能指向后继(next)节点,又可以指向前驱(prior)节点

在这里插入图片描述

typedef int ELEM_TYPE;
//双向链表的结构体设计
typedef struct DNode
{
	ELEM_TYPE data;
	DNode* next;
	DNode* prior;
}DNode, * PDNode;
1.初始化
void Init_Double_List(DNode* head)
{
    assert(head != NULL);
    if(NULL == head) exit(1);
    head->next = NULL;
    head->prior = NULL;
}
2.插入
1.头插法

在这里插入图片描述

void Insert_head(DNode* head,ELEM_TYPE val)
{
    assert(NULL != head);
    if(NULL == head) exit(1);
    
    //申请新节点
	DNode* pnewnode = (DNode*)malloc(sizeof(DNode));
    if(NULL == pnewnode) exit(1);
    pnewnode->data = val;
    
    //插入
    //先修改申请节点的两个指针域,再修改插入位置的右边节点指针域,最后修改插入位置的左边节点的指针域
    pnewnode->next = head->next; // 1
    pnewnode->prior = head; //2
    //空链表不存在节点,所有不需要将其前驱指向pnewnode
    if(!Is_Empty(head))
        pnewnode->next->prior = pnewnode;//3
    head->next = pnewnode;//4
    return true;
    
}
2.尾插法

在这里插入图片描述

bool Insert_tail(PDNode head,ELEM_TYPE val)
{
    //任何情况下都是修改三个指针域,也即待插入节点的两个域和上一个节点的next域
    assert(NULL != head);
    if(NULL == head) exit(1);
    
    //1.申请新节点
    DNode* pnewnode = (DNode*)malloc(sizeof(DNode));
    if(NULL == pnewnode) exit(1);
    pnewnode->data = val;
    
    //2.找插入位置
    DNode* p = head;
    while(p->next != NULL)
        p = p->next;
    //3.插入
    pnewnode->next = p->next;
    pnewnode->prior = p;
    p->next = pnewnode;
    return true;
}
3.按位置插

在这里插入图片描述

bool Insert_pos(DNode* head,ELEM_TYPE val,int pos)
{
    assert(NULL != head);
    assert(pos >= 0 && pos <= Get_Length(head));
    if(NULL == head) exit(1);
    //对pos判断,如果pos=0,则调用头插函数
    //			如果pos=length,则调用尾插函数
    if(pos == 0) return Insert_head(head,val);
    if(pos == Get_Length(head)) return Insert_tail(head,val);
    
    //1.申请新节点
    DNode* pnewnode = (DNode*)malloc(sizeof(DNode));
    if(NULL == pnewnode) exit(1);
    pnewnode->data = val;
    //2.找插入位置
	DNode* p = head;
    for(int i = 0; i < pos; i++)
        p = p->next;
    //3.插入
    pnewnode->next = p->next;//1
    pnewnode->prior = p;//2
    pnewnode->next->prior = pnewnode;//3
    p->next = pnewnode;//4
}
3.删除
1.头删
bool Del_head(DNode* head)
{
    //普通情况:只需要修改两个指针域
    //特殊情况:只有一个节点,此时只需要修改一个指针域
	assert(NULL != head);
    if(NULL == head) exit(1);
    //判空
    if(Is_Empty(head)) return false;
    //1.申请新节点
    DNode* pnewnode = (DNode*)malloc(sizeof(DNode));
    if(NULL == pnewnode) exit(1);
    //2.申请临时指针q,让其分别指向待删除节点
    DNode* q = head->next;
    //3.跨越指向+释放内存
    head->next = q->next;
    //如果链表节点大于1,那么还需要将后面节点指向头节点以达到双向链表
    if(q->next != NULL)
        q->next->prior = head;
    free(q);
    q = NULL;
    return true;
    
}
2.尾删

尾删只需要修改一个指针域,也就是倒数第二个节点的next域

bool Del_tail(DNode* head)
{
    assert(head != NULL);
    if(NULL == head) exit(1);
    //判空
	if(Is_Empty(head)) return false;
    //申请新节点
	DNode* pnewnode = (DNode*)malloc(sizeof(DNode));
    //2.申请两个临时变量p和q,让其分别指向待删除节点的前驱和待删除节点
    DNode* q = head;
    while(q->next != NULL)
        q = q->next;
    DNode* p = q->pripr;
    //3.跨越指向+释放内存
    p->next = q->next;
    free(q);
    q = NULL;
    return true;
}
3.按位置删
bool Del_Pos(DNode* head,int pos)
{
    //这里的pos类似于数组下标,第一个元素的pos = 0
    assert(NULL != head);
    assert(pos >= 0 && pos < Get_Length(head));//有这一步就不需要再判空了
    if(NULL == head) exit(1);
    //申请新节点
    DNode* pnewnode = (DNode*)malloc(sizeof(DNode));
    if(NULL == pnewnode) exit(1);
    //提前处理头尾节点
    if(pos == 0) return Del_head(head);
    if(Pos == Get_Length(head) - 1) return Del_tail(head);
    //2.申请两个临时变量p和q,让其分别指向待删除节点的前驱和待删除节点
    DNode* p = head;
    for(int i = 0; i < pos; i++)
        p = p->next;
    DNode* q = p->next;
    //跨越指向+释放内存
    p->next = q->next;
    q->next->prior = p;
    
    free(q);
    q = NULL;
    return true;
    
}
4.按值删
//删除val第一次出现的节点
bool Del_val(DNode* head,ELEM_TYPE val)
{
    assert(NULL != head);
    if(NULL == head) exit(1);
    //判空
    if(Is_Empty(head)) return false;
    DNode* q = Search(head,val);//查找有没有这个值
    if(q == NULL) return false;
    if(head->next == q)
        return Del_head(head);
    
    DNode* p = q->prior;//待删除节点的前驱
    p->next = q->next;//跨越指向
    if(q->next != NULL) //实现双向链表 如果为空则不用
        q->next->prior = p;
    free(q);
    q = NULL;
    return true;
}

//删除所有val值节点
bool Del_val_all(DNode* head, ELEM_TYPE val)
{
	//0.assert
	assert(NULL != head);
	if (NULL == head)
		exit(EXIT_FAILURE);
	//0.5 判空
	if (Is_Empty(head))
		return false;
	DNode* p = head->next;
	while (p != NULL)
	{
		//提前保存下一个地址,cur可能会被释放
		DNode* q = p->next;
		if (p->data == val)
		{
			p->prior->next = p->next;
			if (p->next != NULL)
				p->next->prior = p->prior;
			free(p);
		}
        //下一个节点
		p = q;
	}
	return true;
}
5.判空与获取有效长度
//判空
bool Is_Empty(DNode* head)
{
    return head->next == NULL;
}

//获取有效长度
int Get_Length(DNode* head)
{
    DNode* p = head->next;
    int count = 0;
    while(p != NULL)
    {
        count++;
        p = p->next;
    }
    
}
6.销毁
void Destroy(DNode* head)
{
    assert(NULL != head);
    //创建临时指针p,让其保存头节点的指针域
    DNode* p = head->next;
    //创建临时指针q,先不赋值
    DNode* q ;
    while(p != NULL)
    {
        //1.q指向p的下一个节点
        q = p->next;
        //2.释放p所指向的节点
        free(p);
        //3.让p指向q所在的节点
        p = q;
    }
    
    //最后将头节点的指针域置空
    head->next = NULL;
}
7.查找
DNode* Search(DNode* head,ELEM_TYPE val)
{
    assert(NULL != head);
    if(NULL == head) exit(1);
    //判空
    if(Is_Empty(head)) return NULL;
    for(DNode* p = head->next; p != NULL; p = p->next)
    {
        if(p->data == val)
            return p;
    }
    return NULL;
}

4.链表相关题目

1.逆置链表

本质上还是头插法,假设头插1,3,5,2,4,遍历链表读取数据顺序为4,2,5,3,1,所以链表逆置可以理解为将原链表节点重新头插给头节点

void ReverseLink(Node* head)
{
    //保存有效链表
    Node* p = head->next;
    if(p == NULL) return ;
    //头节点断开,开始重新头插
    head->next = NULL;
    while(p != NULL)
    {
        Node* q =p->next;
        //p指向的节点进行头插
        p->next = head->next;
        head->next = p;
        p = q;
    }
}
2.求单链表的倒数第k个节点

定义两个指针pre和p都指向头节点,先让p走k步,然后pre和p同步向前走,直到p指向空,则此时pre指向的节点恰好是倒数第k个节点

也就是让pre走n-k步,p总共走n步。

p = k + (n - k) , pre = n - k;,即倒数第k个节点

Node* Get_LastK_Node(Node* head,int k)
{
    Node* p = head;
    Node* pre = head;
    for(int i = 0; i < k; i++)
    {
        p = p->next;
        if(p == NULL) return NULL;//长度不够
    }
    while(p != NULL)
    {
		pre = pre->next;
    	p = p->next;
    }
    return pre;
}
3.有序单链表合并

定义两个指针p,q分别指向两个链表的第一个有效节点

定义last指向已连接的末尾节点

void MergeLink(Node* head1,Node* head2)
{
    Node* p = head1->next;
    Node* q = head2->next;
    Node* last = head1;
    head2->next = NULL;//用不到head2了
    while(p != NULL && q != NULL)
    {
        //选择较小的节点
        if(p->data < q->data)
        {
            last->next = p;//重新指向
            p = p->next;//更新p
            last = last->next;//last始终指向末尾
        }
        else
        {
            last->next = q;
            q = q->next;
            last = last->next;
		}
    }
    //当一个节点为空之后,直接将last指向下一个非空指针,即接上非空的链表
    if(q != NULL) last->next = q;
    else last->next = p;
    
}
4.判断单链表有没有环,如果有,返回入环点
Node* Get_Entry_loop_point(Node* head)
{
    //先判断有没有环
    Node* fast = head;
    Node* slow = head;
    while(fast != NULL && fast->next != NULL)
    {
		//先各自走一步
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)//k
        {
            //2.根据公式X = (n-1)(Y+Z)+Z
    		Node* p = head;
    		Node* q = fast;
   			 while(p != q)
    		{
        		p = p->next;
        		q = q->next;
    		}
    		return p;
        }
    }
    return NULL;//没有环
}

&& q != NULL)
{
//选择较小的节点
if(p->data < q->data)
{
last->next = p;//重新指向
p = p->next;//更新p
last = last->next;//last始终指向末尾
}
else
{
last->next = q;
q = q->next;
last = last->next;
}
}
//当一个节点为空之后,直接将last指向下一个非空指针,即接上非空的链表
if(q != NULL) last->next = q;
else last->next = p;

}


#### 4.判断单链表有没有环,如果有,返回入环点

```c
Node* Get_Entry_loop_point(Node* head)
{
    //先判断有没有环
    Node* fast = head;
    Node* slow = head;
    while(fast != NULL && fast->next != NULL)
    {
		//先各自走一步
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)//k
        {
            //2.根据公式X = (n-1)(Y+Z)+Z
    		Node* p = head;
    		Node* q = fast;
   			 while(p != q)
    		{
        		p = p->next;
        		q = q->next;
    		}
    		return p;
        }
    }
    return NULL;//没有环
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值