数据结构_单链表SinglyLinkedList(C++实现

数据结构_SinglyLinkedList单链表(C++实现

前言:此类笔记仅用于个人复习,内容主要在于记录和体现个人理解,详细还请结合bite课件、录播、板书和代码。

前言&注意事项
  1. 单链表C++的实现分为了结点类和链表类两个类,十分明了,可读性很高,也很容易写,节点类负责单个节点的操作,链表负责链表整体的操作
实现

SList.h

#include <iostream>
#include <cassert>

using namespace std;

class outofBound
{
}; //越界异常信号类

template <class elemType>
class sList; //类的向前说明,因为下面的node类将sList看作友元类,在此提前声明一下sList类的存在
//前面要声明参数模板,类后面不用跟参数模板

template <class elemType>
class node //节点类
{
	friend class sList<elemType>;//之后的sList中要用到node的私有成员data和next,只有将sList看作了友元类才行在sList中(属于node类外)访问node的私有成员
  //友元类后面一定要跟参数模板

private:
	elemType data;
	node *next;

public:
	node() : next(NULL) {}					//默认构造函数
	node(const elemType &e, node *N = NULL) //含缺省参数的构造函数
	{
		data = e;
		next = N;
	}
};

这里一定要注意为什么提前声明sList以及为什么node类将sList类看作友元类

还要注意

类的向前声明的时候类后面不加模板参数,但是前面一定要有参数模板的声明;

友元类后面一定要有模板参数

就按上面的写法

template <class elemType>
class sList //单链表类
{
private:
	node<elemType> *head;

public:
	sList(); //构造函数,建立一个空链表

	bool sListempty() const; //判空,空则返回真,否则返回假

	// bool sListFull()const;//判满(额,我倒是想知道怎么判断是满的,没什么用这个

	int sListLength() const; //返回链表长度

	void sListPushBack(elemType &e); //尾插

	void sListPushHead(elemType &e); //头插

	void sListPopBack(); //尾删

	void sListPopHead(); //头删

	void sListInsert(int pos, elemType &e); //在pos位置插入e(在pos结点的前面插入e

	void sListErase(int pos); //删除pos结点

	elemType sListGet(int pos); //返回pos处的节点的值

	int sListFind(elemType &e); //返回值等于e的结点的序号,没有匹配的则返回0

	void sListRemove(int pos, elemType &e); //移除pos处的数据并赋值给e

	void sListReverse() const; //单链表元素就地逆置

	void sListClear(); //清空单链表,保留head结点,释放其余空间

	~sList(); //析构函数,销毁单链表,释放所有空间
};

SList.cpp

包含头文件以及构造函数的定义

#include "sList.h"

template <class elemType>
sList<elemType>::sList() //构造函数,建立一个空链表
{
	head = new node<elemType>(); //开辟一个结点对象空间(这里new的对象最后面这里加了括号,加不加括号的区别请看code日记)
}
template <class elemType>
bool sList<elemType>::sListempty() const //判空,空则返回真,否则返回假
{
	assert(head);

	return head->next == NULL;
}
template <class elemType>
int sList<elemType>::sListLength() const //返回链表长度
{
	assert(head);

	int size = 0;
	node<elemType> *p = head->next; //这里是让p=head->next这样可以统计有效节点的个数
	while (p)
	{
		size++;
		p = p->next;
	}
	return size;
}
template <class elemType>
void sList<elemType>::sListPushBack(elemType &e) //尾插
{
	assert(head);

	node<elemType> *New, *p = head; //这里让p=head,因为有链表为空的情况

	New = new node<elemType>(e); //构造了一个data初始化了的匿名对象
	while (p->next)				 //找到最后一个节点
	{
		p = p->next;
	}
	p->next = New;
}
template <class elemType>
void sList<elemType>::sListPushHead(elemType &e) //头插
{
	assert(head);

	node<elemType> *New, *p = head->next;

	New = new node<elemType>(e);
	head->next = New;
	New->next = p;
}
template <class elemType>
void sList<elemType>::sListPopBack() //尾删
{
	assert(head);

	node<elemType> *p = head;
	if (head->next == NULL)		  //如果链表是空的
		return;					  //直接返回上一级函数
	if (head->next->next == NULL) //如果只有一个有效节点,不用找尾
	{
		delete head->next;
		head->next = NULL;
	}
	else
	{
		while (p->next->next) //找尾
		{
			p = p->next;
		}
		delete p->next;
		p->next = NULL;
	}
}
template <class elemType>
void sList<elemType>::sListPopHead() //头删
{
	assert(head);

	node<elemType> *p = head->next;
	if (head->next == NULL) //如果链表是空的
		return;				//直接返回上一级函数
	else
	{
		head->next = p->next;
		delete p;
	}
}
template <class elemType>
void sList<elemType>::sListInsert(int pos, elemType &e) //在pos位置插入e(在pos节点的前面插入e
{
	assert(head);

	if (pos < 1) //如果pos小于1则非法访问(越界
		throw outofBound();

	node<elemType> *p = head, *New;
	int j = 0;
	while (p && j < pos - 1) // p为空说明走到了链表的最后面,也就是NULL
	{
		j++;
		p = p->next;
	}

	if (!p)
		throw outofBound();
	// return;
	New = new node<elemType>(e, p->next);
	p->next = New;
}

​ p的位置是在pos位的前一个,数据插在pos的位置就是插在p的后面,如果p在尾节点,插入在p的后面是没问题的,如果p走到了节点的尾部也就是尾结点的next也就是NULL,那说明pos肯定是超出了范围,否则在此之间p就能走到pos的前一个位置而终止循环

​ 插入是可以尾插的,即pos的位置是在最后一个节点的后面,或者说NULL的前面,此时p在尾结点

​ while里面j的作用是判断有没有走到pos的位置之前,如果j=pos-1,说明p走到了pos的前一个位置

​ 如果数组长度为7,pos的位置是8,那么在p走到最后一个节点的时候,j=7,p就停止移动,此时相当于尾插

​ 如果数组的长度是7,pos的位置是9,那么在p走到最后一个节点的时候,j=7,p不为空,j<8,p继续后移到NULL,此时走了到链表的尽头,说明了链表长度不够,要插入的位置是在NULL还往后,这是非法访问

template <class elemType>
void sList<elemType>::sListErase(int pos) //删除pos节点
{
	assert(head);

	if (pos < 1)
		throw outofBound();

	node<elemType> *p = head;
	int j = 0;
	while (p->next && j < pos - 1) // p->next为空说明p走到了最后一个节点,如果走到了最后一个节点,说明pos的位置越界
	{
		j++;
		p = p->next;
	}

	if (!p->next)
		throw outofBound();
	node<elemType> *tmp = p->next; // p是pos的前一个节点
	p->next = tmp->next;
	delete tmp;
}

删除只能删除存在的节点,如果删除超过长度的节点就是非法的,所以pos只能访问存在的节点的位置,即在NULL之前,head之后

template <class elemType>
elemType sList<elemType>::sListGet(int pos) //返回pos处的节点的值
{
	assert(head);

	if (pos < 1)
		throw outofBound();

	int j = 0;
	node<elemType> *p = head;

	while (p->next && j < pos - 1)
	{
		j++;
		p = p->next;
	}

	if (!p->next)
		throw outofBound();
	return p->next->data;
}

也是只能访问存在的节点的位置

template <class elemType>
int sList<elemType>::sListFind(elemType &e) //返回值等于e的结点的序号,没有匹配的则返回0
{
	assert(head);

	int i = 1;

	node<elemType> *p = head->next;
	while (p)
	{
		if (p->data == e)
			return i;
		i++;
		p = p->next;
	}
	return 0;
}
template <class elemType>
void sList<elemType>::sListRemove(int pos, elemType &e) //移除pos处的数据并赋值给e
{
	assert(head);

	if (pos < 1)
		throw outofBound();

	node<elemType> *p = head;
	int j = 0;
	while (p->next && j < pos - 1)
	{
		j++;
		p = p->next;
	}

	if (!p->next)
		return;
	node<elemType> *tmp = p->next; // p是pos的前一个节点
	p->next = tmp->next;
	e = tmp->data; //将pos出的data赋值给引用e
	delete tmp;
}
template <class elemType>
void sList<elemType>::sListReverse() const //单链表元素就地逆置 (这个比较重点
{
	assert(head);

	node<elemType> *p, *q; //兄弟协同
	p = head->next;
	head->next = NULL;
	while (p)
	{
		q = p->next;
		p->next = head->next;
		head->next = p; //首席插入
		p = q;
	}
}
IMG_0576 IMG_0577(20220913-081601) img
template <class elemType>
void sList<elemType>::sListClear() //清空单链表,保留head结点,释放其余空间
{
	assert(head);

	node<elemType> *p = head->next;

	while (p)
	{
		head->next = p->next;
		delete p;
		p = head->next;
	}
}
template <class elemType>
sList<elemType>::~sList() //析构函数,销毁单链表,释放所有空间
{
	sListClear();
	delete head;
	head == NULL;
}
总结
  1. 头部操作比尾部操作简单,因为尾部操作需要先找尾,找尾还要看有效节点是一个还是多个,一个的话就不用找,多个的话就要找
  2. 这里我犯了一个糊涂,混淆了一些东西,解释一下:
  1. new开辟动态内存之后返回的是地址
  2. 访问对象的成员的时候的格式是 对象.成员 ;使用指针来访问对象成员的时候是 对象指针->成员

这一点是因为,类和对象是一种特殊的结构体(自定义类型),结构体访问成员就是

结构体名.结构体成员

或者

结构体指针->结构体成员

  1. 匿名对象名后面无论加不加初始化参数都要有括号(C++异常处理有说明)
  2. 一定要写对单词!我写顺序表因为main写成mian找了很长时间的错误,写单链表因为friend写成了frind又找了很久错误
练习

1.约瑟夫环

n个人围成一个圈,从1、2、3开始报数。当报到m时,第m个人出列,并从原来的第m+1人重新开始1、2、3报数。如此循环,直到圈中只剩下一个人。这个圈称为约瑟夫环。试用单向循环链表实现该游戏,并输出最后剩下的那人的姓名。

#include<iostream>
#include<string.h>
using namespace std;

struct Node//单链表结点
{
	Node* next;
	string name;
};

struct Ring//约瑟夫环结构体(单向循环链表)
{
	Node* head;
	Node* tail;
};

void ThrowOut(Ring* ring, int n, int m)//用于让第m个人出列
{
	if (n < m)
		return;
	int j = 1;
	Node* temp=ring->head;
	Node* x=NULL;

	for (j; j < m-1; j++)
	{
		temp = temp->next;
	}

	//cout << temp->next->name << endl;//输出每次出列的人的名字
	x = temp->next;
	temp->next = temp->next->next;
	free(x);

	ring->tail = temp;
	ring->head = temp->next;

}

void ringGame(Ring* ring,int n)//约瑟夫环游戏实现函数
{
	while (ring->head == ring->tail)
	{
		int m;
		cout << "Please keynode the number you want to throw out" << endl;
		cin >> m;
		ThrowOut(ring, n, m);
	}
	cout <<"The last member's name is:  "<< ring->head->name << endl;
}

2.已知表头元素为c的单链表在内存中的存储状态如下表所示

地址
1000Ha1010H
1004Hb100CH
1008Hc1000H
100CHdNULL
1010He1004H
1014H

现将f存放于1014H处并插入到单链表中,若f在逻辑上位于a和e之间,则a,e,f的“链接地址”依次是什么?

原来逻辑上:c,a,e,b,d

现在逻辑上:c,a,f,e,b,d

则a的链接地址(后继指针)变成了f的地址,f的链接地址变成了e的,e的不变

即a->next=1014H, f->next=1010H, e->next=1004H

3.集合的并、差(不一定是有序的集合

利用链式结构分别实现集合运算
C = A ∪ B 、 C = A − B = A − A ∩ B C=A\cup B、C=A-B=A-A\cap B C=ABC=AB=AAB
并分析其时间复杂度。要求运算结束后在内存中的A、B两个集合中的元素不变

思路:

求并集的时候,可以先将A、B简单相加得C,然后删除C中数据重复的结点

求差时候,以A为基础,A中的每个结点和B比较,A、B中有相同的就不插入C,把A中具有且B中没有的插入到C

#include<iostream>
using namespace std;

struct Node//单链表结点
{
	Node* next;
	int data;//方便起见就假设集合是一个数集吧~~
};

class CT//集合结构体,CT是collection的缩写
{
private:
	Node* head;
public:
	CT();
	void And(CT*, CT*);
	void Difference(CT*, CT*);
};

CT::CT()
{
	Node* C = new Node();
	head = C;
	C->next = NULL;
	C->data = 0;
}

void CT::And(CT* A, CT* B)//求并集:A+B-AB
{
	if (A == NULL || B == NULL )
		return;
	Node* a = A->head;
	Node* b = B->head;
	Node* c = head;

	while (a)
	{
		c->data = a->data;
		c->next = new Node();
		c = c->next;
		c->next = NULL;
		a = a->next;
	}
	while (b)
	{
		c->data = b->data;
		c->next = new Node();
		c = c->next;
		c->next = NULL;
		b = b->next;
	}
	c = head;
	while (c)
	{
		Node* x = c;
		Node* y = c->next;
		while (y)
		{
			if (c->data != y->data)
			{
				x = x->next;
				y = y->next;
			}
			else
			{
				x->next = y->next;
				delete(y);
				y = x->next;
			}
		}
		c = c->next;
	}

}

void CT::Difference(CT* A, CT* B)//求差集,遍历A中的每一个元素的时候遍历B中的每一个元素,有相同的就不加入到C,没有就加入到C
{
	Node* a = A->head;
	Node* c = head;

	if (A == NULL || B == NULL)
		return;

	while (a)
	{
		Node* b = B->head;
		while (b)
		{
			if (a->data == b->data)
			{
				break;
			}
			else
			{
				b = b->next;
			}
		}
		if (b == NULL)
		{
			c->data = a->data;
			c->next = new Node();
			c = c->next;
			c->next = NULL;
		}
		a = a->next;
	}
}
题目

为了不重新写一个单链表,下面这些函数都是直接在上面👆写好的单链表头文件(sList.h)中声明的,并在另一个文件中定义的

1.求两个递增单链表的交、并、差集,并且要求结果也是递增的单链表

请用两种方案实现:一种是用原有空间,一种是用新的空间

用原有空间的话,就是以A为主链,将A作为结果

//交集就是在A中只保留下来AB都有的
template <class elemType>
void sList<elemType>::intersection(sList &b)
{
node<elemType> *i, *j;
i = head;
j = b.head->next;

while (i->next && j)
{
if (i->next->data == j->data)
{
    i = i->next;
    j = j->next;
  }
  else
  {
      if (i->next->data < j->data)
      {
          node<elemType> *k = i->next;
          i->next = i->next->next;
          delete (k);
          k = NULL;
      }
      else
          j = j->next;
  }
}
}
//这里要注意,因为涉及删除,所以a中要用两个指针,或者指针指向被判断的元素的前一个,指针的next指向被判断的元素,为删除做准备
//并集就是把 B中有且A中没有的 给A
template <class elemType>
void sList<elemType>::unionSet(sList &b)
{
node<elemType> *i, *j;
i = head->next;
j = b.head->next;

while (i->next && j)
{
if (i->data == j->data)
{
    i = i->next;
    j = j->next;
  }
  else
  {
      if (i->data < j->data)
      {
          if (i->next->data > j->data)
          {
              node<elemType> *k = j->next;
              j->next = i->next;
              i->next = j;
              j = k;
              i = i->next->next;
          }
          else
              i = i->next;
      }
      else
          j = j->next;
  }
}
while (j)
{
  if (i->data != j->data)
{
    i->next = j;
    i = i->next;
  }
  j = j->next;
}
}
//差集就是保留A中有且B中没有的
template <class elemType>
void sList<elemType>::complementarySet(sList &b)
{
node<elemType> *i, *j;
i = head;
j = b.head->next;
while (i->next && j)
{
if (i->next->data == j->data)
{
    node<elemType> *k = i->next;
    i->next = i->next->next;
      delete (k);
      k = NULL;
  }
  else
  {
      if (i->next->data < j->data)
      {
          i = i->next;
      }
      else
      {
          j = j->next;
      }
  }
}
}

用申请新空间的方案:也就是用另外一个链表C作为结果链

//并集就是将A、B都有的才放入C
template <class elemType>
void sList<elemType>::intersectio_new(sList &a, sList &b)
{
node<int> *i = a.head->next;
node<int> *j = b.head->next;

while (i && j)
{
if (i->data == j->data)
{
    sListPushBack(i->data);
    i = i->next;
      j = j->next;
  }
  else
  {
      if (i->data < j->data)
          i = i->next;
      else
          j = j->next;
  }
}
}
//交集就是A有B没有,放入,B有A没有,放入,AB都有,放入
template <class elemType>
void sList<elemType>::unionSet_new(sList &a, sList &b)
{
node<int> *i = a.head->next;
node<int> *j = b.head->next;

while (i && j)
{
if (i->data == j->data)
{
    sListPushBack(i->data);
    i = i->next;
      j = j->next;
  }
  else
  {
      if (i->data < j->data)
      {
          sListPushBack(i->data);
          i = i->next;
      }
      else
      {
          sListPushBack(j->data);
          j = j->next;
      }
  }
}
while (i)
{
  sListPushBack(i->data);
i = i->next;
}
while (j)
{
  sListPushBack(j->data);
j = j->next;
}
}
//A中有B中没有的放入C <或者简单一点,调用交集函数然后将A中的减去交集>
template <class elemType>
void sList<elemType>::complementarySet_new(sList &a, sList &b)
{
sList<int> K;
K.intersectio_new(a,b);
node<int>*k=K.head->next;
node<int>*i=a.head->next;
 while(i)
 {
     if(i->data!=k->data)
     {
         sListPushBack(i->data);
         i=i->next;
     }
     else
     {
         i=i->next;
         k=k->next;
     }
 }
}

2.已知线性表中的元素以值递增有序排列,并以单链表做存书结构。请写一种高效的算法,删除表中所有值大于mink且小于maxk的元素(如果表中有这样的元素),同时释放被删除的结点空间,并分析一下算法的时间复杂度

用双指针就可以解决,一个i在前,一个j在后

i先找到区间(大于mink小于maxk的区间)之前的第一个元素(也就是小于mink的元素里面的最后一个元素)(此时i->data<=mink , i->next->data>=mink)

然后j开始往后找到区间之后的第一个元素(也就是j一直往后,直到j->data>=mark)

template <class elemType>
void sList<elemType>::interval(int mink, int maxk)
{
 node<int> *i, *j;
 j = i = head->next;

 while (i->next)
 {
     if (i->data <= mink && i->next->data <= mink)
         i = i->next;
     else
         break;
 }
 j = i;
 while (j->next)
 {
     if (j->data < maxk )
         j = j->next;
     else
         break;
 }
 while (i->next != j)
 {
     node<int> *k = i->next;
     i->next = i->next->next;
     delete k;
     k = NULL;
 }
}
结束

That’s all, thanks for reading!💐

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值