数据结构——单链表

来源:互联网 发布:旅游沙盘教学软件 编辑:程序博客网 时间:2024/05/18 02:59

前面介绍了顺序表的特点和实现!但是顺序表有很多的不足,比如要给顺序表的前面插入一个数据,就必须先把后面的数据先一个一个的往后挪动,然后再将所要插入的数据放进去。就相当于一个数组一样。


还有就是顺序表的大小分配,如果采用静态分配内存的方式,那么势必就会造成剩余内存的浪费,不利于CPU工作。要是用动态内存分配,可以减少内存浪费情况,但是一次性开辟的内存不能太大也不能太小,遇到一个结构体数据刚刚存放满,这时候在想增加数据就要再申请空间,那么申请的空间只用了一个,其他的也就造成了浪费。

所以这时候为了方便存储,和更加节省空间,就采用了单链表的方式。先来看看为什么单链表有这么多好处


单链表采用上一个节点的头结点和下个节点相邻,相当于把好多的数据直接连起来,用的时候只需要改变节点之间的连接。而且增容是一次增加一个节点的大小,实现了真正的用多少分配多少。

写单链表的时候一定要注意你的首节点,别丢了,丢了就找不回来。还有就是在删除节点的时候一定要对那个节点负责,不然让它成为野指针了,对谁都不好。


单链表如何实现,还有接口怎么封装,具体在程序中看一下,重点部分都做了标识。

ListNode.h

#ifndef _LISTNODE_H_#define _LISTNODE_H_#include <stdio.h>#include <windows.h>#include <assert.h>typedef int Datatype;typedef struct ListNode{Datatype data;struct ListNode *next;}ListNode;void PushBack(ListNode **pplist, Datatype x);//从后面插入一个data为x的节点void PopBack(ListNode **plist);//从后面删除一个节点void PrintList(ListNode *plist);  //打印单链表void PushFront(ListNode **pplist, Datatype x); //从前面插入一个data为x的节点void PopFront(ListNode **pplist);//从前面删除一个节点ListNode* Find(ListNode* pList, Datatype x); //查找data为x的节点,并返回这个节点//在pos前插入||删除void Insert(ListNode **pplist, ListNode *pos, Datatype x);//在所指定的节点前插入一个data为x的节点void Erase(ListNode **pplist, ListNode *pos);//删除指定的节点#endif


ListNode.cpp

#include "ListNode.h"ListNode *BuyNode(Datatype x)//生成节点封装接口{ListNode *Node = (ListNode *)malloc(sizeof(ListNode));// 以动态的形式生成一个大小为一个结构体大小的节点Node->data = x;//所要生成的节点数据Node->next = NULL;//将生成节点的下个赋为空return Node;}void PrintList(ListNode *plist)//打印单链表{ListNode *cur = plist;while(cur != NULL){printf("%d->",cur->data);//将单链表的数据正向输出cur = cur->next;} printf("NULL\n");}void PushBack(ListNode **pplist, Datatype x)//在做这些之前要先理清思路,都有哪几种情况,然后敲{//1.空 2.只有一个 3.多个assert(pplist);if(*pplist == NULL)//没有节点的时候直接把节点给首节点{*pplist = BuyNode(x);}else if((*pplist)->next == NULL){(*pplist)->next = BuyNode(x);//只有一个就把生成的节点串在首节点后面}else{ListNode *cur = *pplist;while((cur)->next != NULL){cur = cur->next;}cur->next = BuyNode(x);}}void PopBack(ListNode **pplist)//和Push一样都要考虑清楚,然后就很好写{//1.NULL 2.1个 3.多个assert(pplist);if(*pplist == NULL){return;}else if((*pplist)->next == NULL){free(*pplist);//只有一个的话直接释放*pplist = NULL;//一定要将这个节点置空,不然容易成为野指针}else{ListNode *tmp = *pplist;//先定义两个指针,分别保存要删除的节点和上一个节点ListNode *cur = tmp;while(tmp->next != NULL){cur = tmp;tmp = tmp->next;}free(tmp);//删除指定节点cur->next = NULL;//将上个节点的next给置空tmp = NULL;//记得置空}}void PushFront(ListNode **pplist, Datatype x)//和尾插一样,只是情况还要具体分析{//1.NULL 2.一个 3.多个assert(pplist);if(*pplist == NULL){*pplist = BuyNode(x);}else{ListNode *cur = BuyNode(x);cur->next = *pplist;*pplist = cur;}}void PopFront(ListNode **pplist){//1.NULL 2.一个 3.多个assert(pplist);if(*pplist == NULL){return;}else if((*pplist)->next == NULL){free(*pplist);*pplist = NULL;}else{ListNode *tmp = *pplist;*pplist = tmp->next;free(tmp);}}ListNode* Find(ListNode* plist, Datatype x)//查找节点并不改变数据,所以不用传结构体指针的地址{while(plist){if(plist->data == x)//当找到要查找的数时,就直接返回{return plist;}plist = plist->next;}//perror("Find");//出错打印错误信息//system("pause");//exit(EXIT_FAILURE);//程序直接退出return NULL;}void Insert(ListNode **pplist, ListNode *pos, Datatype x)//先调Find{//1.NULL 2.在第一个前插或只有一个数据 3.多个assert(pplist);assert(pos);if(*pplist == NULL){*pplist = BuyNode(x);}else if(((*pplist)->next == NULL) || (pos == *pplist)){PushFront(pplist, x);}else{ListNode *tmp = *pplist;//这里要找到指定插入节点的上个节点和下个节点while(tmp->next != pos){tmp = tmp->next;}ListNode *p = BuyNode(x);tmp->next = p;p->next = pos; }}void Erase(ListNode **pplist, ListNode *pos)//要删除首先就要调用Find函数查找到相应的节点{//1.NULL或1个 2.多个assert(pplist);assert(pos);if(((*pplist) == NULL) || ((*pplist)->next) == NULL)//只有一个或者没有节点时直接采用前删{PopFront(pplist);}else{ListNode *tmp = *pplist;while(tmp->next != pos){tmp = tmp->next;}tmp->next = pos->next;free(pos);pos = NULL;}}

test.cpp

#include "ListNode.h"struct ListNode* plist;void test1(){PushBack(&plist, 1);PushBack(&plist, 2);PushBack(&plist, 3);PushBack(&plist, 4);PopBack(&plist);PopBack(&plist);PopBack(&plist);PopBack(&plist);PopBack(&plist);PrintList(plist);}void test2(){PushFront(&plist, 1);PushFront(&plist, 2);PushFront(&plist, 3);PushFront(&plist, 4);PrintList(plist);PopFront(&plist);PopFront(&plist);PopFront(&plist);PopFront(&plist);PopFront(&plist);PrintList(plist);}void test3(){ListNode *pos = NULL;PushBack(&plist, 1);/*PushBack(&plist, 2);PushBack(&plist, 3);PushBack(&plist, 4);*/pos = Find(plist, 2);Insert(&plist, pos, 5);//Erase(&plist, pos);PrintList(plist);}int main(){//test1();//test2();test3();system("pause");return 0;}


为了更加明确的了解链表的增删查改,在Linux环境下来演示一下具体的过程

#include "ListNode.h"struct ListNode* plist;void test1(){    int i = 0;    int j = 0;    printf("尾插,尾删\n");    printf("\n");    while(i<5){        PushBack(&plist, i++);        PrintList(plist);        sleep(1);    }    while(j<5){        PopBack(&plist);        PrintList(plist);        sleep(1);        j++;    }}void test2(){    int i = 0;    int j = 0;    printf("前插,前删\n");    printf("\n");    while(i<5){        PushFront(&plist, i);        PrintList(plist);        sleep(1);        i++;    }while(j<5){        PopFront(&plist);        PrintList(plist);        sleep(1);        j++;    }}void test3(){    int i = 0;    ListNode *pos = NULL;    printf("链表的节点插入、删除和查找\n");    printf("\n");    while(i<5){        PushBack(&plist, i++);        PrintList(plist);        sleep(1);    }    pos = Find(plist, 2);    printf("查找单链表里数据为2的节点i\n");    printf("给2的前面插入5\n");    Insert(&plist, pos, 5);    PrintList(plist);    sleep(1);    printf("删除2\n");    Erase(&plist, pos);    PrintList(plist);    }int main(){        test1();        test2();        test3();        return 0;}




原创粉丝点击