0
点赞
收藏
分享

微信扫一扫

android studio 批量修改包名 app package name

落拓尘嚣 2024-09-28 阅读 15
数据结构

目录


今天要记录的是线性表的单链表,上节内容提到过顺序表存取是随机的非常快捷;但是不方便插入或者改变容量,所以这里就提出了单链表的概念

一、什么是单链表,如何定义单链表

单链表简单地说就是将一个数据元素分为数据域和指针域,指针域用于存放当前数据元素的后继地址,方便找到后面的元素
在这里插入图片描述
在介绍单链表的操作之前,我们要先了解两个知识点

  1. typedef关键字
    这是用于对数据类型进行重命名的关键字,可以使后面使用这个数据类型时更加方便,它的使用语法如下:
    在这里插入图片描述
    学会这个语法之后我们就可以定义一个单链表:
typedef struct LNode
{
	int data;//存放一个数据元素
	LNode* next;//指向下一个节点的指针
}LNode,*Linklist;//将struct LNode重命名为LNode和*Linklist

其实这块代码和下面这块代码是一样的

struct LNode
{
	int data;//存放一个数据元素
	LNode* next;//指向下一个节点的指针
};
typedef struct LNode LNode;//将struct LNode重命名为LNode
typedef struct LNode* Linklist;//将struct LNode重命

从这里就可以看出typedef关键字的方便之处了;另外也许有人会疑惑为什么要为同一个结构类型重命名两个名字,其实这里是为了提高代码可读性,Linklist代表创建一个链表,而LNode指一个节点

  1. 单链表分为有头节点和无头节点;总的来说有头节点的单链表操作会更加的简单,下面分别对有头节点和没有头节点的单链表进行初始化

没有头节点

bool init_list(Linklist& l)
{
	l = NULL;//没有任何节点
	return true;
}

有头节点

bool init_list(Linklist&l)
{
	l = new LNode;//分配一个头节点
	if (l == NULL)return false;//内存不足,分配失败
	l->next = NULL;//分配成功,头节点置空
	return true;
}

二、单链表的插入和删除

单链表的插入可以分为按位序插入和按值插入,我们这里就只将带头结点和不带头节点的按位插入

1.按位插入

bool insert_list(Linklist& l,int i,int e)
{
	if (i < 1)return false;//检查i是否符合逻辑
	LNode* p=l;//用p来指向头节点遍历到i-1
	int j=0;//计数之用,方便循环
	while (j < i - 1 &&p!=NULL)
	{
		p = p->next;//不断遍历
		j++;
	}
	//做一个超出范围的检查
	if (p == NULL) { cout << "超出插入范围" << endl; return false; }
	LNode* s=new LNode;//s作为新节点插入
	s->data = e;
	s->next = p->next;
	p->next = s;
	return true;//插入成功
}

在这里插入图片描述

不带头节点就会在处理第一个节点时略微麻烦一点,接下来就看代码实操:

bool insert(Linklist& l, int i, int e) {  
    // 如果索引无效(小于1),则返回false  
    if (i < 1) return false;  
  
    // 如果在链表头部插入(i == 1)  
    if (i == 1) {  
        LNode* s = new LNode(e); // 使用构造函数初始化数据  
        s->next = l;  
        l = s;  
        return true;  
    }  
  
    // 找到插入点的前一个节点  
    int j = 1;  
    LNode* p = l;  
    while (j < i - 1 && p != nullptr) {  
        p = p->next;  
        j++;  
    }  
  
    // 如果索引超出链表长度,则返回false  
    if (p == nullptr) return false;  
  
    // 在找到的位置插入新节点  
    LNode* s = new LNode(e);  
    s->next = p->next;  
    p->next = s;  
    return true;  
}

这没有头指针的麻烦处理就在于指向首元节点的是p指针还是l,有头节点就可以将其一致处理,用p指针就可以直接进行衔接

2.指定节点的前插操作

由于链表的特性,指定节点都只能找到它后面的元素,因为指针域存放的都是后面的地址,如果要想找到其前面的元素就只有找到链表名字(头指针)通过遍历来找到它的前驱节点,但这样它的时间复杂度就是O(n)了
在这里插入图片描述
所以这里引进一种新思路,既然我们无法直接进行前驱节点的寻找,那么我们就传入要插入的数据和当前节点,在当前节点p后面插入一个节点并将p中的数据复制过来然后将要传入的数据放在p中,这样就实现了一个前插操作,并且时间复杂度还是O(1)下面是代码演示:

bool infrontinsert(LNode* p,int e)
{
	if (p == NULL)return false;
	LNode* s = new LNode{p->data,p->next};//分配内存并将p节点复制过来
	if (s == NULL)return false;//分配内存失败
	p->data = e;//p节点存放新数据
	p->next = s;//更新p节点的指向
	return true;
}

3.按位删除带头结点的节点

同样我们删除某个节点时也有两种想法,最普通的就是我们用遍历来找到要删除的前驱节点,改变它的指针域使它跳过当前节点,最后释放当前节点的内存就完成了删除操作

1.遍历删除节点

下面是代码演示:

bool delete_lnode(Linklist&l,int i)
{
	//判断范围不合法以及空表的情况
	if (i < 1||l->next==NULL)return false;
	int j = 0;//用于计数遍历
	LNode* p=l;//当前指针遍历链表
	while (j < i - 1 && p ->next!= NULL)
	{
		p = p->next;
		j++;
	}
	if (p->next == NULL)return false;//i超出范围
	LNode* q = p->next;
	p->next = p->next->next;
	delete q;
	return true;
}

2.替身法

这和前面的前插法有点类似,由于单链表的局限性(无法逆向检索),我们可以用替身的办法进行删除节点,因为我们无法改变目标节点的前驱节点的指针域,所以目标节点是无法释放的(如果释放链表就会断开),那么我们就把后继节点复制到当前目标的数据域(覆盖),这样我们可以将后继节点进行删除,这样也不会出现地址丢失的情况,缺点是如果我们要删除最后一个节点需要单独处理,因为最后一个节点没有后继节点进行替身了,下面是代码实现:

bool delete_lnode(LNode*& p)
{//如果p是最后一个节点就直接删除
	if (p->next == NULL) { delete p; p = NULL; return true; }

	//p后面还有节点,进行替换删除操作
	LNode* s = p->next;
	p->data = s->data;
	p->next = s->next;
	delete s;
	return true;
}

这里我对删除最后一个节点的情况进行了判断处理;另外这里涉及一个问题:在我尝试不适用引用时这个指针是无法被修改指向的,因为对于这个指针本身仍然是值传递,所以我们如果想要删除这个指针我们就要使用引用或者二级指针

三、单链表的查找

1.按位查找

按位查找其实在前面插入部分已经实现过了,这里我们只需要修改i为当前要提取的元素就可以了,但是需要注意的是如果查找失败我们需要返回一个空,而且在溢出时我们也会返回一个空

//按位查找(带头结点)
LNode* getelem(Linklist l, int i)
{
	if (i < 1||l->next==NULL)return NULL;
	int j = 0;
	LNode* p = l;
	while (j < i && p != NULL)
	{
		p = p->next;
		j++;
	}
	return p;
}

2.按值查找

LNode* getelem2(Linklist l, int e)  
{  
	// 初始化指针p,使其指向链表的第一个实际数据节点(即头节点的下一个节点)  
	LNode* p = l->next;  
	  
	// 循环遍历链表,直到找到数据域等于e的节点或遍历完整个链表  
	while (p != NULL && p->data != e)  
	{  
		p = p->next;  
	}  
	  
	// 返回找到的节点指针,如果未找到则返回NULL  
	return p;  
}

3.统计表长

int length(Linklist l)
{
	int i = 0;
	LNode* p = l;
	while (p->next != NULL)
	{
		p = p->next;
		i++;
	}
	return i;
}

四、单链表的建立

建立一个单链表通常有两种方法:头插法和尾插法,这里我们就依次介绍

1.尾插法

其实用尾插法我们可以调用后插法的函数,不过这样我们就要每次都遍历一次,最后时间复杂度达到了O(n^2),因此这里我们需要定义一个尾指针,这样每次插入一个元素尾指针就指向这个元素,不用每次都遍历,可以降低时间复杂度

void tailinsert(Linklist& l)
{
	init_list(l);//初始化一个有头节点的链表
	int x;
	LNode* p=NULL,*r=l;//p用于创建新节点,r作为哨兵节点
	cin >> x;
	while (x != 0)
	{
		p = new LNode{ x,NULL };//创建新节点并初始化
		r->next = p;//连接新节点
		r = r->next;//这里等价r=s;用于指向新的表尾节点
		cin >> x;
	}
}

2.头插法

void headinsert(Linklist&l)
{
	init_list(l);
	LNode* p = NULL;
	int x=0;
	cin >> x;
	while (x != 0)
	{
		p = new LNode{x,l->next};
		l->next = p;
		cin >> x;
	}
}

五、单链表的销毁和清空

1.销毁

void destroylist(Linklist& l)
{
	LNode* p = l;
	while (l)
	{
		l = l->next;
		delete p;
		p = l;
	}
}

2.清空

void empty(Linklist& l)
{
	LNode* p = l->next, * q = l->next;
	while (p)
	{
		q = q->next;
		delete p;
		p = q;
	}
	l->next = NULL;
}

六、各种功能源码

typedef struct LNode
{
	int data;//存放一个数据元素
	LNode* next;//指向下一个节点的指针
}LNode,*Linklist;//将struct LNode重命名为LNode和*Linklist
bool init_list(Linklist& l)
{
	l = new LNode;
	if (l == NULL)return false;
	l->next = NULL;
	return true;
}
bool insert_list(Linklist& l,int i,int e)
{
	if (i < 1)return false;//检查i是否符合逻辑
	LNode* p=l;//用p来指向头节点遍历到i-1
	int j=0;//计数之用,方便循环
	while (j < i - 1 &&p!=NULL)
	{
		p = p->next;//不断遍历
		j++;
	}
	//做一个超出范围的检查
	if (p == NULL) { cout << "超出插入范围" << endl; return false; }
	LNode* s=new LNode;//s作为新节点插入
	s->data = e;
	s->next = p->next;
	p->next = s;
	return true;//插入成功
}
bool insert(Linklist& l, int i, int e)
{
	if (i < 1)return false;
	if (i == 1)
	{
		LNode* s = new LNode;
		s->data = e;
		s->next = l;
		l = s;
		return true;
	}
	int j=1;
	LNode* p = l;
	while (j < i - 1 && p != NULL)
	{
		p = p->next;
		j++;
	}
	if (p == NULL)return false;
	LNode* s = new LNode;
	s->data = e;
	s->next = p->next;
	p->next = s;
	return true;
}
bool infrontinsert(LNode* p,int e)
{
	if (p == NULL)return false;
	LNode* s = new LNode{p->data,p->next};
	if (s == NULL)return false;
	p->data = e;
	p->next = s;
	return true;
}
bool delete_lnode(Linklist&l,int i)
{
	//判断范围不合法以及空表的情况
	if (i < 1||l->next==NULL)return false;
	int j = 0;//用于计数遍历
	LNode* p=l;//当前指针遍历链表
	while (j < i - 1 && p ->next!= NULL)
	{
		p = p->next;
		j++;
	}
	if (p->next == NULL)return false;//i超出范围
	LNode* q = p->next;
	p->next = p->next->next;
	delete q;
	return true;
}
bool delete_lnode(LNode*& p)
{//如果p是最后一个节点就直接删除
	if (p->next == NULL) { delete p; p = NULL; return true; }

	//p后面还有节点,进行替换删除操作
	LNode* s = p->next;
	p->data = s->data;
	p->next = s->next;
	delete s;
	return true;
}
//按位查找(带头结点)
LNode* getelem(Linklist l, int i)
{
	if (i < 1||l->next==NULL)return NULL;
	int j = 0;
	LNode* p = l;
	while (j < i && p != NULL)
	{
		p = p->next;
		j++;
	}
	return p;
}
LNode* getelem2(Linklist l, int e)
{
	LNode* p=l->next;
	while (p->data != e&&p!=NULL)
	{
		p = p->next;
	}
	return p;
}
int length(Linklist l)
{
	int i = 0;
	LNode* p = l;
	while (p->next != NULL)
	{
		p = p->next; 
		i++;
	}
	return i;
}
void tailinsert(Linklist& l)
{
	init_list(l);//初始化一个有头节点的链表
	int x;
	LNode* p=NULL,*r=l;//p用于创建新节点,r作为哨兵节点
	cin >> x;
	while (x != 0)
	{
		p = new LNode{ x,NULL };//创建新节点并初始化
		r->next = p;//连接新节点
		r = r->next;//这里等价r=s;用于指向新的表尾节点
		cin >> x;
	}
}
void headinsert(Linklist&l)
{
	init_list(l);
	LNode* p = NULL;
	int x=0;
	cin >> x;
	while (x != 0)
	{
		p = new LNode{x,l->next};
		l->next = p;
		cin >> x;
	}
}
void print(Linklist l)
{
	LNode* p=l;
	while (p)
	{
		p = p->next;
		cout << p->data << "\t";
	}
	cout << endl;
}
void destroylist(Linklist& l)
{
	LNode* p = l;
	while (l)
	{
		l = l->next;
		delete p;
		p = l;
	}
}
void empty(Linklist& l)
{
	LNode* p = l->next, * q = l->next;
	while (p)
	{
		q = q->next;
		delete p;
		p = q;
	}
	l->next = NULL;
}
int main()
{
	Linklist l;
	headinsert(l);
	print(l);
}
举报

相关推荐

0 条评论