数据结构笔记二_线性表的链式存储结构(C++)

本文介绍了如何使用C++模板类和内嵌结构实现单链表,包括链表类的定义、构造函数、基本操作(长度、搜索、访问、插入、删除和清空)以及遍历。实例演示了如何在链表中插入和删除元素,以及一个实际应用案例:字符串首字符删除并插入中间位置。
摘要由CSDN通过智能技术生成

线性表

线性表的基本定义和顺序存储的实现见上一篇博客:https://blog.csdn.net/weixin_51352359/article/details/120497500

线性表的链式存储结构——以单链表为例

线性表的抽象类

首先依据线性表要实现的功能,定义线性表的类模板即父类(当然这一步也可以不做):

template<class elemType>
class list {
public:
	//对于属性类的函数,因其并不改变原数组or链表的值,在后面加const
	//可以达到保护隐含this指针以及供常对象使用的目的
	virtual int length() const = 0;
	virtual int search(const elemType& x) const = 0;
	//const &结构:在数据类型未知or比较复杂时,可以提高运行效率,同时节省空间
	virtual elemType visit(int i) const = 0;

	//数据操纵类函数
	virtual void insert(int i, const elemType& x) = 0;
	virtual void remove(int i) = 0;
	virtual void clear() = 0;

	//遍历操作
	virtual void traverse()const = 0;

	//析构函数,防止内存泄露
	virtual ~list() {};
};

单链表类的定义

关于单链表的一些基本概念,可以参考以前的博客:
https://blog.csdn.net/weixin_51352359/article/details/120471969

单链表的存储映像图如下:
在这里插入图片描述
描述单链表只需要一个指向头结点的指针型变量——头指针,这里用head指针。

  • 利用内嵌类
//单链表类的定义
template<class elemType>
class linkList :public list<elemType> {
private:
	struct node {//内嵌类
		elemType data;
		node* next;
	//构造函数
	node(const elemType& x, node* p = NULL) { data = x; next = p; }
	node():next(NULL){}
	//析构函数
	~node(){}
	}
	node* head;
public:
	linkList();
	~linkList() { clear(); delete head; }
	int length() const;
	int search(const elemType& x)const;
	elemType visit(int i)const;
	void insert(int i, const elemType& x);
	void remove(int i);
	void clear();
	void traverse()const;
};

基本操作实现

  • 构造类
  1. 构造函数
template<class elemType>
linkList<elemType>::linkList() {
	head = new node();
}
  • 属性类
  1. 求链表的长度
    从首节点开始,沿着后继指针链一个一个往后数,数到一个节点长度加1
template<class elemType>
int linkList<elemType>::length() {
	int count = 0;
	node* p = head->next;//指向首节点
	while (p != NULL) {
		count++; p = p->next;
	}
	return count;
}
  1. 搜索某个元素在线性表中是否存在,并返回元素下标
template<class elemType>
int linkList<elemType>::search(const elemType& x)const {
	//首节点位置为0
	int pos = -1;
	node* p = head->next;
	while (p) {
		pos++;
		if (p->data == x)break;
		p = p->next;
	}
	if (!p) return-1;//并未找到
	return pos;
}
  1. 访问某个元素
template<class elemType>
elemType linkList<elemType>::visit(int i)const {
	//判断i是否出界,若出界则直接跳出
	if (i<0 || i>length()) throw OutOfBound();
	int count = 0;
	node* p = head->next;
	while (p) {
		if (count == i)break;
		else {
			count++;
			p = p->next;
		}
	}
	return p->data;
}
  • 操纵类
  1. 在位置i插入某个元素x
    首先考虑最简单的情况,即在首节点后插入一个节点(首席插入法)
tmp = new node(x);//新建节点
tmp->next = head->next;//使自己指向下一个节点
head->next = tmp;//使上一个节点指向自己,链入完毕

之后考虑一般情况,即在第i个位置插入元素。由上述可知,在定位到第i-1个元素后,元素的插入就变得很简单了,故主要分为一下几步:

  • new一个新节点tmp存储元素
  • 使指针p指向第i-1个元素
  • 链入链表
//在第i-1个后面插入
template<class elemType>
void linkList<elemType>::insert(int i, const elemType& x) {
	if (i<0 || i>length()) throw OutOfBound();
	node* tmp;
	tmp = new node(x);//node的构造函数
	//使p指向第i-1个节点
	int count = 0;
	p = head;
	while (count < i) {
		p = p->next;
		count++;
	}
	//链入tmp
	tmp->next = p->next;
	p->next = tmp;
}
  1. 删除第i个位置的元素
    同上,删除首节点最为简单:
node* tmp = head->next;
if (!tmp)return;//注意: tmp可能为空!!
head->next = tmp->next;
delete tmp;

之后进入到一般情况:

template<class elemType>
void linkList<elemType>::remove(int i) {
	if (i<0 || i>=length()) throw OutOfBound();
	//让p指向第i-1个位置
	node* q,*p = head;
	int count = 0;
	while (count < i) {
		p = p->next;
		if (!p)return;//如果p为空,则直接返回 和上面的边界判定其实有重合
		count++;
	}
	q = p->next;
	if (!q) return;
	p->next = q->next;
	delete q;
}
  1. 清除一个线性表clear()
template<class elemType>
void linkList<elemType>::clear() {
	//第一种方法:永远删首节点
	node* p = head->next;
	while (p) {
		head->next = p->next;
		delete p;
		p = head->next;
	}
	head->next = NULL;
	/*
	//第二种方法:兄弟协同法
	node* p = head->next, * q;
	head->next = NULL;
	while (p) {
		q = p->next;
		delete p;
		p = q;
	}
	*/
	}
  • 遍历类:即将线性表中的所有元素输出
template<class elemType>
void linkList<elemType>::traverse() {
	node* p = head->next;
	while(p)
	{
		cout << p->data << " ";
		p = p->next;
	}
}

代码集合清单

  • 头文件
    并未使用父类,主要利用友元实现,其中要注意node类的使用。如若不用友元,代码中也提供了另一种实现方式。
//文件名:linklist.h
#include<iostream>
using namespace std;

class outOfBound {};

/*
//类模板的定义
//在使用这种情况时,node可以直接使用,而不用使用node<elemType>
template<class elemType>
class linkList {
private:
	struct node {
		elemType data;
		node* next;
		node(const elemType& x, node* p = NULL) { data = x; p = next; }
		node() :next(NULL) {}
		~node() {}
	};
	node* head;
public:
	linkList();
	~linkList() { clear(); delete head; }
	int length()const;
	int search(const elemType& x)const;
	elemType visit(int i)const;
	void insert(int i, const elemType& x);
	void remove(int i);
	void clear();
	void traverse()const;
};
*/

//如果用下面这种方式定义,node需用node<elemType>
template<class elemType>
class linkList;//前声明
template<class elemType>
class node {
	friend class linkList<elemType>;
private:
	elemType data;
	node* next;
public:
	node(const elemType& x, node* p = NULL) {
		data = x; next = p;
	}
	node() :next(NULL) {}
};

template<class elemType>
class linkList {
private:
	node<elemType>* head;
public:
	linkList();
	~linkList() { clear(); delete head; }
	int length()const;
	int search(const elemType& x)const;
	elemType visit(int i)const;
	void insert(int i, const elemType& x);
	void remove(int i);
	void clear();
	void traverse()const;
};


//基本操作实现
template<class elemType>
linkList<elemType>::linkList() {
	head = new node<elemType>();
}

template<class elemType>
int linkList<elemType>::length() const{
	//首节点长度为1
	int count = 0;
	node<elemType>* p = head->next;
	while (p) {
		count++;
		p = p->next;
	}
	return count;
}

template<class elemType>
int linkList<elemType>::search(const elemType& x)const {
	//首节点下标为0
	int count = 0;
	node<elemType>* p = head->next;
	while (p) {
		if (p->data == x)break;
		else count++;
		p = p->next;
	}
	if (!p)return -1;
	else return count;
}

template<class elemType>
elemType linkList<elemType>::visit(int i)const {
	//首节点下标为0
	if (i<0 || i>length() - 1) throw outOfBound();
	int count = 0;
	node<elemType>* p = head->next;
	while (p) {
		if (count == i) break;
		count++;
		p = p->next;
	}
	return p->data;
}

template<class elemType>
void linkList<elemType>::insert(int i, const elemType& x) {
	if (i<0 || i>length()) throw outOfBound();
	node<elemType>* tmp = new node<elemType>(x);
	node<elemType>* p = head;
	int count = 0;
	while (p) {
		if (count == i) break;
		count++;
		p = p->next;
	}
	tmp->next = p->next;
	p->next = tmp;
}

template<class elemType>
void linkList<elemType>::remove(int i){
	if (i<0 || i>length()-1) throw outOfBound();
	int count = 0;
	node<elemType>* p = head;
	while (p) {
		if (count == i) break;
		count++;
		p = p->next;
	}
	node<elemType>* tmp = p->next;
	p->next = tmp->next;
	delete tmp;
}

template<class elemType>
void linkList<elemType>::clear() {
	node<elemType>* p = head->next;
	head->next = NULL;
	if (!p) return;
	node<elemType>* q = p->next;
	while (q) {
		delete p;
		p = q;
		q = p->next;
	}
	delete p;
}

/*
template<class elemType>
void linkList<elemType>::clear() {
	node* p = head->next;
	while (p) {
		head->next = p->next;
		delete p;
		p = head->next;
	}
}
*/

template<class elemType>
void linkList<elemType>::traverse() const {
	node<elemType>* p = head->next;
	while (p) {
		cout << p->data << " ";
		p = p->next;
	}
}
  • 主函数
    功能测试,将一个字符串的首字符删除并插入到中间位置
#include<iostream>
#include"linklist.h"
using namespace std;

int main() {
	linkList<char> chlist;

	char ctemp;
	int i, n, result;

	cout << "number of the elements:" << endl;
	cin >> n;
	cin.get(ctemp);//将enter抛弃

	cout << "input the elements:\n" << endl;
	//将字符逐个输入到表中,并依次插入表尾
	for (i = 0; i < n; i++) {
		cin.get(ctemp);
		chlist.insert(i, ctemp);
	}
	ctemp = chlist.visit(0);//获得首节点
	chlist.remove(0);//将首节点删除
	chlist.insert((chlist.length() + 1) / 2, ctemp);//将删除的首节点放在中间位置

	cout << "output the elements:\n" << endl;
	for (i = 0; i < chlist.length(); i++) {
		cout.put(chlist.visit(i));//读取第i个节点的值并输出
	}
	cout << endl;
	cout << endl;
	chlist.traverse();//遍历输出
	cout << endl;
	cout << endl;
	cout << chlist.search('a');
	return 0;
}
  • 代码运行结果:
    在这里插入图片描述

扩展:就地逆置

利用首席插入法和兄弟协同法实现单链表的就地逆置:
(下图为具体思路图示)
在这里插入图片描述
代码清单:

template<class elemType>
void linkList<elemType>::reverse() {
	node<elemType>* p, *q;

	p = head->next;
	head->next = NULL;

	while (p) {
		q = p->next;
		p->next = head->next;
		head->next = p;
		p = q;
	}
}

代码运行结果(最后一行为上一行的reverse()):
在这里插入图片描述

应用:求集合交集

#include<iostream>
#include"linklist.h"
using namespace std;

int main() {
    linkList<int> list1, list2, list3;
    int i = 0, j, x;
    int len1;

    //输入第一个集合中的元素
    cout << "请输入第一个集合中元素:(以0作为结束标志)";
    cin >> x;
    while (x != 0)
    {
        list1.insert(i, x);
        i++;
        cin >> x;
    }

    //输入第二个集合的元素
    i = 0;
    cout << "请输入第二个集合中元素:(以0作为结束标志)";
    cin >> x;
    while (x != 0)
    {
        list2.insert(i, x);
        i++;
        cin >> x;
    }

    //查找list1中的元素是否在list2中出现
    //如果出现则将其存入list3中
    len1 = list1.length();
    int num = 0;
    for (j = 0; j < len1; ++j) {
        int y = list1.visit(j);
        if (list2.search(y) != -1) {
            list3.insert(num, y);
            num++;
        }
    }

    //输出list3
    list3.traverse();
}

运行结果:
在这里插入图片描述

补充:双链表的实现

在这里插入图片描述
基本要素:

  • node* head
  • node*tail
  • currentlength

代码清单:

//文件名:DlinkList.h
#include<iostream>
using namespace std;

template<class elemType>
class DlinkList {
private:
	struct node {
		elemType data;
		node* prior, * next;
		node(const elemType& x, node* p = NULL, node* q = NULL) {
			data = x; prior = p; next = q;
		}
		node():next(NULL),prior(NULL){}
		~node(){}
	};
	node* head, * tail;
	int currentlength;
	node* move(int i)const;//返回第i个节点的地址
public:
	DlinkList();
	~DlinkList() { clear(); delete head; delete tail; }
	void clear();
	int length()const { return currentlength; }
	int search(const elemType& x)const;
	elemType visit(int i)const;
	void insert(int i, const elemType& x);
	void remove(int i);
	void traverse();
	void reverse();
};

template<class elemType>
DlinkList<elemType>::DlinkList() {
	head = new node;
	head->next = tail = new node;
	tail->prior = head;
	currentlength = 0;
}

template<class elemType>
typename DlinkList<elemType>::node* DlinkList<elemType>::move(int i)const {
	node* p = head->next;
	while (i--) p = p->next;
	return p;
}

template<class elemType>
int DlinkList<elemType>::search(const elemType& x)const {
	node* p = head->next;
	int i;
	for (i = 0; i < currentlength; ++i) {
		if (p->data == x)break;
		p = p->next;
	}
	if (i == currentlength)return-1;
	else return i;
}

template<class elemType>
elemType DlinkList<elemType>::visit(int i)const {
	return move(i)->data;
}

template<class elemType>
void DlinkList<elemType>::clear() {
	node* p, * q;
	p = head->next;
	while (p!=tail) {
		q = p->next;
		delete p;
		p = q;
	}
	head->next = NULL;
	tail->prior = head;
	currentlength = 0;
}

template<class elemType>
void DlinkList<elemType>::insert(int i, const elemType& x) {
	node* p, * tmp;
	p = move(i);//找到第i个节点
	tmp = new node(x, p->prior, p);
	p->prior->next = tmp;
	p->prior = tmp;
	++currentlength;
}

template<class elemType>
void DlinkList<elemType>::remove(int i) {
	node* p;
	p = move(i);
	p->prior->next = p->next;
	p->next->prior = p->prior;
	delete p;
	--currentlength;
}

template<class elemType>
void DlinkList<elemType>::traverse() {
	node* p = head->next;
	while (p != tail) {
		cout << p->data << " ";
		p = p->next;
	}
	cout << endl;
}

template<class elemType>
void DlinkList<elemType>::reverse() {
	node* p;

	for (p = tail->prior; p != head; p = p->prior) {
		cout << p->data;
	}
}

例题

题目描述
给定一个N个数的数组,M次操作,每次操作为下列操作之一。求最后的数组。

操作1:在第X个数之后插入一个数Y。

操作2:删除第X个数。

输入格式
第一行:两个整数N,M(N,M≤100000)含义见试题描述。

第二行:N个整数,表示原来的数组。

接下来M行,每行第一个数OP,表示操作类型。

对于操作1,接下来两个数X,Y,含义见题面描述,保证0≤X≤当前数的个数,若X=0,表示在数组开头插入。

对于操作2,接下来一个数X,含义见题面描述,保证1≤X≤当前数的个数。

输出格式
输出若干个数,表示最后的数组。
样例输入
5 3
1 2 3 4 5
1 1 6
2 1
2 2
样例输出
6 3 4 5

代码清单:

#include<iostream>
using namespace std;

struct ListNode {
	int value;
	ListNode* next;
	ListNode(int val) :value(val), next(NULL) {};
	ListNode() :value(0), next(NULL) {};
};

void _insert(ListNode* head, int pos, int value) {
	ListNode* pre = head, * cur = head;

	for (int i = 0; i <= pos; ++i) {
		pre = cur;
		cur = cur->next;
	}

	ListNode* to_insert = new ListNode(value);
	pre->next = to_insert;
	to_insert->next = cur;
}

void _delete(ListNode* head, int pos) {
	ListNode* pre = head, * cur = head;

	for (int i = 1; i <= pos; ++i) {
		pre = cur;
		cur = cur->next;
	}
	pre->next = cur->next;
	cur = cur->next;
}

int main() {
	int n, m;
	cin >> n >> m;

	ListNode* head = new ListNode(),*cur = head;

	for (int i = 0; i < n; i++) {
		int t;
		cin >> t;
		ListNode* tmp = new ListNode(t);
		cur->next = tmp;
		cur = cur->next;
	}

	int c;
	for (int i = 0; i < m; ++i) {
		cin >> c;

		if (c == 1) {
			int insert_value,pos;
			cin >> pos >> insert_value ;
			_insert(head, pos, insert_value);
		}
		else {
			int pos;
			cin >> pos;
			_delete(head, pos);
		}
	}

	ListNode* p = head->next;

	while (p) {
		cout << p->value <<" ";
		p = p->next;
	}

	return 0;
}

运行结果:
在这里插入图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值