大话数据结构(C++版)链表

基础知识

单链表有两种实现方式,带头结点和不带头结点

每个结点除了存放数据元素外,还要存储指向下一个结点的指针

顺序表优点:可随机存储,存储密度高

           缺点:要求大片连续空间,改变容量不方便

单链表优点:不要求大片连续空间,改变容量方便

           缺点:不可随机存取,要耗费一定空间存放指针

前提代码

#include <iostream>
using namespace std;

#define MAXSIZE 20 // 存储空间初始分配量 


struct Node  //将用于表示链表中的节点
{
    int data; //每个结点存放一个数据元素
    Node* next;  //表示指向下一个节点的指针
};

typedef Node* LinkList;  //Node*表示指向Node结构体的指针类型,而LinkList是用来代表这个指针类型的别名。
//为了提高程序的可读性,在此对同一结构体指针类型起了两个名称,LinkList 与 LNode*,
//两者本质上是等价的。通常习惯上用 LinkList 定义单链表,强调定义的是某个单链表的头指针;
//用 LNode* 定义指向单链表中任意结点的指针变量。例如,若定义 LinkList L,则 L 为单链表的头指针,
//若定义 LNode* p,则 p 为指向单链表中某个结点的指针,用 *p 代表该结点。
//当然也可以使用定义 LinkList p,这种定义形式完全等价于 LNode* p。 

打印整数

 bool visit(int c)
{
    cout << c;
    return true;
}

初始化链式线性表

bool InitList(LinkList *L) 
{ 
    //*L=(LinkList)malloc(sizeof(Node)); /* 产生头结点,并使L指向此头结点 */
    *L = new Node;  //用于创建一个新的节点,并将该节点的地址赋值给指针变量*L,即链表的头节点指针。
    if (!(*L)) /* 存储分配失败 */
        return 0;
    (*L)->next = NULL; /* 指针域为空 */

    return true;
}
//if (!(*L)) 检查 *L 是否为 NULL。如果 *L 是 NULL,那么 !(*L) 就为 true,if 语句中的代码块就会被执行。如果 *L 不是 NULL,那么 !(*L) 就为 false,if 语句中的代码块就不会被执行。

带头结点更方便

检查是否空表

#include <iostream>
using namespace std;


struct Node
{
    int data;
    Node* next;
};

typedef Node* LinkList;  // 定义链表类型
bool InitList(LinkList* L)
{
    *L = new Node;
    if (!(*L))
        return false;
    (*L)->next = nullptr;
    
    return true;
}

bool ListEmpty(LinkList L)
{
    if (L->next)
        return false;
    else
        return true;
}

int main()
{
    LinkList L;
    InitList(&L);

    if (ListEmpty(L))
        cout << "The list is empty." << endl;
    else
        cout << "The list is not empty." << endl;

    return 0;
}

重置空表

bool ClearList(LinkList* L) //是一个指向 LinkList 类型的指针的指针。这意味着 L 指向一个指针,而这个指针指向链表的头结点。
{
    LinkList p, q;  //声明了两个 LinkList 类型的指针,p 和 q。
    p = (*L)->next;  //*L 表示对指针 L 所指向的对象进行解引用。如果 L 是一个指针,那么 *L 就是这个指针指向的实际对象。

    while (p)  //这个循环条件表示只要p指向的节点不为空,即链表还有节点需要释放,就继续执行循环。
    {
        q = p->next;  //将下一个节点的指针保存到临时指针变量q中,以便在释放当前节点后能够访问到下一个节点。
        delete p;  // 使用delete操作符释放当前节点p的内存空间,即释放节点所占用的内存。
        p = q;  //将指针p移动到下一个节点,即将p指向q所指向的节点,以便在下一次循环中处理下一个节点。
    }//通过这个循环,可以逐个释放链表中的节点,直到链表为空

    (*L)->next = NULL;  // 头节点的指针域置为空

    return true;  // 返回操作成功
}
//p和q是临时指针变量,用于遍历链表节点。
//p指向链表的第一个节点,即头节点的下一个节点。
//使用while循环遍历链表,直到遍历到链表尾部,即p为NULL。
//在循环中,首先将下一个节点的指针保存到q中,然后使用delete释放当前节点p的内存空间。

返回L中数据元素个数

int ListLength(LinkList L)
{
    int i = 0;
    LinkList p = L->next; /* p指向第一个结点 */

    while(p)                        
    {
        i++;
        p = p->next;
    }

    return i;
}

用e返回L中第i个数据元素的值(按位查找)

bool GetElem(LinkList L,int i,int *e)
//在函数GetElem中,L作为参数传入,它代表了整个链表。由于链表的操作通常都是从链表的头部开始的,
//所以L一开始就指向头结点,这样设计是为了后续操作的便利。
{
	int j;
	LinkList p;		/* 声明一结点p */
	p = L->next;		/* 让p指向链表L的第一个结点 */
	j = 1;		/*  j为计数器 */

	while (p && j<i)  /* p不为空或者计数器j还没有等于i时,循环继续 */
	{   
		j++;
        p = p->next;  /* 让p指向下一个结点 */		
	}

	if ( !p || j>i ) 
		return 0;  /*  第i个元素不存在 */
	*e = p->data;   /*  取第i个元素的数据 */

	return true;
}

返回L中第1个与e满足关系的数据元素的位序(按值查找)

#include <iostream>
using namespace std;

struct LNode
{
    int data;
    LNode* next;
};

int LocateElem(LNode* L, int e)
{
    int i = 0;
    LNode* p = L->next;

    while (p)
    {
        i++;
        if (p->data == e)  /* 找到这样的数据元素 */
            return i;
        p = p->next;
    }

    return 0;  // 未找到指定元素,返回0
}

int main()
{
    // 创建链表
    LNode* L = new LNode;
    L->next = nullptr;

    // 在链表中插入一些元素
    for (int i = 1; i <= 5; i++)
    {
        LNode* newNode = new LNode;
        newNode->data = i;
        newNode->next = L->next;
        L->next = newNode;
    }

    // 查找元素位置并输出结果
    int position = LocateElem(L, 3);
    if (position != 0)
        cout << "元素3的位置是:" << position << endl;
    else
        cout << "未找到元素3" << endl;

    // 释放链表节点的内存
    LNode* p = L;
    while (p)
    {
        LNode* temp = p;
        p = p->next;
        delete temp;
    }

    return 0;
}

在L中第i个位置之后插入新的数据元素e,L的长度加1

bool ListInsert(LinkList *L,int i,int e)
{ 
	int j;
	LinkList p,s;
	p = *L;   
	j = 1;
//将指向链表头节点的指针 *L 赋值给指针 p。这样做是为了在后续的代码中使用指针 p 来遍历链表。
	while (p && j < i)     /* 寻找第i个结点 */
	{
		p = p->next;
		++j;
	} 

	if (!p || j > i) 
		return 0;   /* 第i个元素不存在 */
	//s = (LinkList)malloc(sizeof(Node));  /*  生成新结点(C语言标准函数) */
    s = new Node;  //变量s是一个指向链表节点的指针,用于表示要插入的新节点
    //创建了一个新的 Node 类型的节点,并将分配的内存地址赋值给指针 s
	s->data = e;  
	s->next = p->next;    //为了将新节点插入到当前节点p之后
	p->next = s;          //为了将新节点插入到链表中,使其成为当前节点p的后继节点。

	return true;
}

前插就是后插再交换

删除L的第i个数据元素,并用e返回其值,L的长度减1

bool ListDelete(LinkList *L,int i,int *e) 
{ 
	int j;
	LinkList p,q;
	p = *L;
	j = 1;

	while (p->next && j < i)	/* 遍历寻找第i个元素 */
	{
        p = p->next;
        ++j;
	}

	if (!(p->next) || j > i) 
	    return false;           /* 第i个元素不存在 */

	q = p->next;  //保存要删除的节点,以便后续操作。
	p->next = q->next;			//跳过节点 q,将链表重新连接起来,实现了删除节点的操作。
	//在链表中,每个节点都有一个指针 next 指向下一个节点。当我们要删除节点 q 时,
    //我们需要将 q 的前一个节点的 next 指针指向 q 的后一个节点,即跳过节点 q。
    *e = q->data;               /* 将q结点中的数据给e */
	free(q);                    /* 让系统回收此结点,释放内存 */

	return true;
}

依次对L的每个数据元素输出

bool ListTraverse(LinkList L)
{
    LinkList p = L->next;

    while(p)
    {
        visit(p->data);
        p = p->next;
    }

    cout<<endl;

    return 1;
}

随机产生n个元素的值,建立带表头结点的单链线性表L(头插法) 

void CreateListHead(LinkList *L, int n) 
{
	LinkList p;
	int i;

	srand(time(0));                         // 初始化随机数种子 
	//*L = (LinkList)malloc(sizeof(Node));
    *L = new Node;
	(*L)->next = NULL;                      //  先建立一个带头结点的单链表 

	for (i = 0; i < n; i++) 
	{
		//p = (LinkList)malloc(sizeof(Node)); //  生成新结点 
        p = new Node;
		p->data = rand()%100+1;             //  随机生成100以内的数字 
		p->next = (*L)->next;    
		(*L)->next = p;						//  插入到表头 
	}
}

随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法) 

void CreateListTail(LinkList *L, int n) 
{
	LinkList p,r;
	int i;
	srand(time(0));                      // 初始化随机数种子 
	//*L = (LinkList)malloc(sizeof(Node)); // L为整个线性表 
    *L = new Node; //创建一个头节点,并将其地址赋值给 L 指针,即将链表的头指针指向头节点。
	r = *L;                           //将 r 指针指向头节点,用于记录链表的尾部。
	
    for(i = 0;i < n;i++)
    {
	    //p = (Node *)malloc(sizeof(Node)); //  生成新结点 
        p = new Node;   //创建一个新节点 p
        p->data = rand()%100+1;    //为新节点的数据域赋值,生成一个介于 1 到 100 之间的随机数。
	    r->next = p;               //将新节点 p 连接到链表的尾部
	    r = p;                // 更新 r 的值为 p 的意思是将指针 r 移动到指针 p 所指向的位置
	}
	r->next = NULL;                       // 表示当前链表结束 
}

int main()
{
    LinkList L;
    ElemType e;
    Status i;
    int j, k;
    i = InitList(&L);
    cout << "初始化L后:ListLength(L)=" << ListLength(L) << endl;
    for (j = 1; j <= 5; j++)
        i = ListInsert(&L, 1, j);
    cout << "在L的表头依次插入1~5后:L.data=";
    ListTraverse(L);

    cout << "ListLength(L)=" << ListLength(L) << endl;
    i = ListEmpty(L);
    cout << "L是否空:i=" << i << "(1:是 0:否)" << endl;

    i = ClearList(&L);
    cout << "清空L后:ListLength(L)=" << ListLength(L) << endl;
    i = ListEmpty(L);
    cout << "L是否空:i=" << i << "(1:是 0:否)" << endl;

    for (j = 1; j <= 10; j++)
        ListInsert(&L, j, j);
    cout << "在L的表尾依次插入1~10后:L.data=";
    ListTraverse(L);

    cout << "ListLength(L)=" << ListLength(L) << endl;

    ListInsert(&L, 1, 0);
    cout << "在L的表头插入0后:L.data=";
    ListTraverse(L);
    cout << "ListLength(L)=" << ListLength(L) << endl;

    GetElem(L, 5, &e);
    cout << "第5个元素的值为:" << e << endl;
    for (j = 3; j <= 4; j++)
    {
        k = LocateElem(L, j);
        if (k)
            cout << "第" << k << "个元素的值为" << j << endl;
        else
            cout << "没有值为" << j << "的元素" << endl;
    }

    k = ListLength(L); /* k为表长 */
    for (j = k + 1; j >= k; j--)
    {
        i = ListDelete(&L, j, &e); /* 删除第j个数据 */
        if (i == 0)
            cout << "删除第" << j << "个数据失败" << endl;
        else
            cout << "删除第" << j << "个的元素值为:" << e << endl;
    }
    cout << "依次输出L的元素:";
    ListTraverse(L);

    j = 5;
    ListDelete(&L, j, &e); /* 删除第5个数据 */
    cout << "删除第" << j << "个的元素值为:" << e << endl;

    cout << "依次输出L的元素:";
    ListTraverse(L);

    i = ClearList(&L);
    cout << "\n清空L后:ListLength(L)=" << ListLength(L) << endl;
    CreateListHead(&L, 20);
    cout << "整体创建L的元素(头插法):";
    ListTraverse(L);

    i = ClearList(&L);
    cout << "\n删除L后:ListLength(L)=" << ListLength(L) << endl;
    CreateListTail(&L, 20);
    cout << "整体创建L的元素(尾插法):";
    ListTraverse(L);

    return 0;
}

单链表的整表删除
 

bool ClearList(LinkList *L)
{
    LinkList p,q;
    p = (*L)->next;
    while(p)
    {
        q = p->next;
        free(p);
        p = q;
    }
    (*L)->next = NULL;
    return true;
}

静态链表前提代码

#include <cstring>
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <ctime>


#define MAXSIZE 1000 /* 存储空间初始分配量 */



bool visit(char c)
{
    cout << c;
    return true;
}

/* 线性表的静态链表存储结构 */
struct Component
{
    char data;
    int cur;  //cur:表示指向下一个元素的索引。
};

typedef struct Component StaticLinkList[MAXSIZE];
//静态链表类型名为StaticLinkList,它是一个数组类型,数组的大小由MAXSIZE定义。
//每个元素都是Component类型的结构体,用于表示链表中的节点。

将一维数组space中各分量链成一个备用链表,space[0].cur为头指针,"0"表示空指针

bool InitList(StaticLinkList space) 
{
	int i;
	for (i = 0; i < MAXSIZE-1; i++)  
		space[i].cur = i+1;
	space[MAXSIZE-1].cur = 0; /* 目前静态链表为空,最后一个元素的cur为0 */
	return true;
}

若备用空间链表非空,则返回分配的结点下标,否则返回0

int Malloc_SSL(StaticLinkList space) 
{ 
	int i = space[0].cur;           		/* 当前数组第一个元素的cur存的值 */
	                                		/* 就是要返回的第一个备用空闲的下标 */
	if (space[0]. cur)         
	    space[0]. cur = space[i].cur;       /* 由于要拿出一个分量来使用了, */
	                                        /* 所以我们就得把它的下一个 */
	                                        /* 分量用来做备用 */
	return i;
}

将下标为k的空闲结点回收到备用链表

void Free_SSL(StaticLinkList space, int k) 
{  
    space[k].cur = space[0].cur;    /* 把第一个元素的cur值赋给要删除的分量cur */
    space[0].cur = k;               /* 把要删除的分量下标赋值给第一个元素的cur */
}

返回L中数据元素个数

int ListLength(StaticLinkList L)
{
    int j = 0;
    int i=L[MAXSIZE-1].cur;
    while(i)
    {
        i = L[i].cur;
        j++;
    }
    return j;
}

在L中第i个元素之前插入新的数据元素e

bool ListInsert(StaticLinkList L, int i, char e)   
{  
    int j, k, l;   
    k = MAXSIZE - 1;   /* 注意k首先是最后一个元素的下标 */
    if (i < 1 || i > ListLength(L) + 1)   
        return false;   
    j = Malloc_SSL(L);   /* 获得空闲分量的下标 */
    if (j)   
    {   
		L[j].data = e;   /* 将数据赋值给此分量的data */
		for(l = 1; l <= i - 1; l++)   /* 找到第i个元素之前的位置 */
		   k = L[k].cur;           
		L[j].cur = L[k].cur;    /* 把第i个元素之前的cur赋值给新元素的cur */
		L[k].cur = j;           /* 把新元素的下标赋值给第i个元素之前元素的ur */
		return true;   
    }   
    return false;   
}

删除在L中第i个数据元素

bool ListDelete(StaticLinkList L, int i)   
{ 
    int j, k;   
    if (i < 1 || i > ListLength(L))   
        return false;   
    k = MAXSIZE - 1;   
    for (j = 1; j <= i - 1; j++)   
        k = L[k].cur;   
    j = L[k].cur;   
    L[k].cur = L[j].cur;   
    Free_SSL(L, j);   
    return true;   
} 

遍历列表

bool ListTraverse(StaticLinkList L)
{
    int j = 0;
    int i = L[MAXSIZE-1].cur;
    while(i)
    {
        visit(L[i].data);
        i = L[i].cur;
        j++;
    }
    return j;
    cout<<"\n";
    return true;
}

int main()
{
    StaticLinkList L;
    Status i;
    i = InitList(L);
    cout<<"初始化L后:L.length=%d\n",ListLength(L);

    i = ListInsert(L,1,'F');
    i = ListInsert(L,1,'E');
    i = ListInsert(L,1,'D');
    i = ListInsert(L,1,'B');
    i = ListInsert(L,1,'A');

    cout << "\n在L的表头依次插入FEDBA后:\nL.data=";
    ListTraverse(L); 

    i = ListInsert(L,3,'C');
    cout << ("\n在L的“B”与“D”之间插入“C”后:\nL.data=";
    ListTraverse(L); 

    i = ListDelete(L,1);
    cout<<"\n在L的删除“A”后:\nL.data=";
    ListTraverse(L); 

    cout<<"\n";

    return 0;
}

双向链表

双向链表的初始化

双链表的插入



双链表的删除

双链表的遍历

循环链表

循环双链表

误区

int main()
{
	Linklist L;
	Initlist(&L);
	Insertlist(&L, 1, 1);
	Insertlist(&L, 2, 2);
	Insertlist(&L, 3, 3);
	Insertlist(&L, 4, 4);
	cout << L;
}//输出链表的地址并不会打印链表的内容。如果要打印链表的内容,
//可以编写一个遍历链表的函数,并使用该函数输出链表的元素值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值