數據結構——單鏈表

前面介紹了順序表的特點和實現!但是順序表有很多的不足,比如要給順序表的前面插入一個數據,就必須先把後面的數據先一個一個的往後挪動,然後再將所要插入的數據放進去。就相當於一個數組一樣。


還有就是順序表的大小分配,如果採用靜態分配內存的方式,那麼勢必就會造成剩餘內存的浪費,不利於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;

}




發佈了43 篇原創文章 · 獲贊 47 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章