链表与表——随心所欲地进出任何队列

                                                      表的定义

如果说有什么数据结构是人人都能想到的,则非表结构莫属。

那么作为一个数据结构的表结构具有什么特征呢?

显然,一个表的本质也是一种数据访问方式:一种可以对标的任意位置进行访问的方式,根据这个思路,可以对表结构给出如下的定义/

表结构是一个某类型数据项组成的有穷序列,它支持下述操作:

(1)     创建一个空表;

(2)     判断一个表是否为空;

(3)     计算一个表中元素的个数;

(4)     清除表中所有元素;

(5)     在特定位置上插入一个元素;

(6)     将特定位置上的元素删除;

(7)     取出特定位置上的元素的值;

(8)     将特定位置上的元素予以替换;

(9)     遍历表里所有元素。

如果将这个定义与栈和队列的定义进行比较,可以发现他们的去区别确实只在数据的访问方式上。由于表结构要支持对任意位置数据的访问,所谓的位置概念就成为了表结构的一个重要概念。即表里面的每个元素有一个位置,只要给出这个位置,就可以对这个元素进行访问。而位置就是一个整数值。

A

B

C

D

E

F

G

H

I

J

(a)  一个表结构

A

B

C

D

E

G

H

I

J

 

(b)删除表结构中的元素F;

                                                    表的实现

与栈和队列一样,表的实现也可以分为连续和链接两种。通常情况下,连续存放的表结构称为线性表;链接实现方式的表结构称为链表。而连续实现的机制当然也是数组。而且,由于数组可以通过下标来对特定元素进行直接访问,数组就成为表的再现。事实上,从某种意义上说,数组就是表,比不过平时所见的数组所支持的操作不如表结构多而已。

下面是线性表的定义。

const int maxlist = 100;     //表的最大尺寸
const int success =0;
const int overflow = 1;
const int underflow = 2;
const int invalid_range = 3;
	
typedef int listEntry;

class list
{
public:
	list(void);
	int size() const;
	bool full() const;
	bool empty() const;
	void clear();
	void traverse(void(*visit)(listEntry&));
	int retrieve(int pos, listEntry &x) const;
	int replace(int pos, const listEntry &x);
	int remove(int pos);
	int insert(int pos, const listEntry &x);
	~list(void);
protected:
	int count;
	listEntry data[maxlist];		//使用数组来存放数据元素

};





线性表的成员函数实现直截了当,学过数组的人都不会有任何问题。下面不加解释的直接给出这些函数的实现。
int list::size() const				//返回表结构里的元素个数
{
	return count;
}

bool list::full() const
{
	return (count == maxlist);
}

bool list::empty() const
{
	return (count == 0);
}

void list::clear()
{
	for (int i = 0; i <= count - 1; i++)
		data[i] = 0;
}

int list::insert(int position, const listEntry &x) //在表位置position上插入元素x
{
	if (full())
		return overflow;
	if (position<0 || position>count)
		return invalid_range;
	for (int i = count - 1; i >= position; i--)
		data[position] = x;
	count++;
	return success;
}

int list::retrieve(int position, listEntry &x) const//取出表位置position上的元素
{
	if (empty())
		return underflow;
	if (position<0 || position>count)
		return invalid_range;
	x = data[position];
	return success;
}

int list::remove(int position)//删除表位置position上的元素
{
	if (empty())
		return underflow;
	if (position<0 || position>count)
		return invalid_range;
	for (int i = position; i <= count - 1; i++)
		data[i] = data[i + 1];
	count--;
	return success;
}

int list::replace(int position, const listEntry&x)//用x替换表位置position上的元素
{
	if (empty())
		return underflow;
	if (position<0 || position>count)
		return invalid_range;
	for (int i = position; i <= count - 1; i++)
		data[position] = x;
	return success;
}

void list::traverse(void(*visit)(listEntry &))//遍历表里所有元素
{
	for (int i = 0; i < count; i++)
		(*visit)(data[i]);
}

list::list(void)
{
	count = 0;
}

list::~list(void)
{
}

表操作的时间成本
     在上述表结构下,对表进行各种操作的成本如下:
如果表里面的记录条数为n,则插入、删除、清除操作的时间成本为O(n),其他的操作为O(1)。

                                                    链表——链接实现的表结构

上面的表结构似乎功能强大,用户可以在任意位置上进行插入、删除和查找,而且实现也非常简单。不过,如果仔细查看,它有着明显的缺点:插入和删除需要移动大批数据,而有时这种操作需要大量的时间成本;此外,还需要大片连续的储存空间。
解决的办法就是采用链接结构。以这种方式构建的表结构就成为链表。

链表的定义如下。

const int success = 0;
const int overflow = 1;
const int underflow = 2;
const int invalid_range = 3;
typedef int nodeEntry;
using namespace std;

struct node       //链表的节点定义
{
	//数据成员
	nodeEntry entry;
	node *next;
};


class list
{
public:
	list(void);
	int size() const;
	bool full() const;
	bool empty() const;
	void clear();
	void traverse(void(*visit)(node&));
	int retrieve(int pos, node &x) const;
	int replace(int pos, const node &x);
	int remove(int pos);
	int insert(int pos, const node &x);
	//为表结构增加的操作符重载及构析函数
	~list(void);
	list(const list &copy);
	void operator = (const list &copy);
protected:
	int count;
	node *head;
	node *setPosition(int position) const;		//辅助函数,用来设置表结构里面的位置

};

这里的数据成员与线性表里的数据成员有一个不同:删除了数据成员,增加了head,它是指向Node的一个指针,而Node的结构就是连接结构的一个节点。
链表虽然将表结构的储存方式改为链接,但试下你的仍然是表结构,连续的表结构提供的操作都需要得到保留。由于是连接结构,还增加了为链接结构而需要的构析函数和重构函数。此外,还增加了一个私有辅助函数setPosition。这个函数的目的是用来设置表中的特定位置,它只被表里面的函数调用,因此为私有函数。




                                                链表

  












下面来实现表结构的各种操作。首先考虑辅助函数setPosition。
前面说股哦,位置是表结构里的一个重要概念。对标就够进行的任何操作都需要转换为针对某个位置进行的操作。在特定位置上插入一个元素,查找一个元素所返回的值也是一个位置索引。因此,链表表结构需要支持的第一个操作就是设置下一步要操作的位子索引,即在链表类里设计的设置函数setPosition。该函数的输入为一个非负整数n,输出则是指向相应节点的指针。由于其输出是指向某数据记录的指针,这个函数必须是一个内部函数,及为类中其他函数所用的函数。
该函数的实现非常简单:从表头开始,一个节点一个节点地数下去,直到数够n个节点为止,然后返回所数到的节点的指针值。下面是该函数的程序实现。
node *list::setPosition(int position)const //返回指向处于position位置上的节点的指针
{
	node *q = head;
	if (position > 1)
		for (int i = 1; i < position; i++)
			q = q->next;
	return q;
}

显然,如果0到count之间的任意整数被作为setPosition函数的参数position的概率相同,则setPosition函数的执行时间成本与表中数据项个数成正比,即O(n)。
有了这个setPosition函数,就可以实现链表结构中的其他函数了。


插入函数的程序实现如下。
int list::insert(int position, node *x)//将元素插入位置position上
{
	if (position < 1)
	{
		cout << "位置不在合法范围内。" << endl;
		return invalid_range;
	}
	node *previous, *following;
	if (position > 1)
	{
		previous = setPosition(position - 1);
		following = previous->next;
	}
	else
		following = head;
	if (position == 1)
		head = x;
	else
	{
		previous->next = x;
		x->next = following;
	}
	count++;
	return success;
}


在链表中进行删除比插入还要简单,只需要将待删除节点的前驱结点指向带删除节点的后继节点即可,如上图。但是味蕾不产生内存泄露,需要将从链表中移除的节点释放。

删除函数的程序实现如下。

int list::remove(int position)//将位置position上的元素删除
{
	if (position<1 || position>count)
	{
		cout << "位置不在合法范围内。" << endl;
		return invalid_range;
	}
	node *previous, *following;
	if (position > 1)
	{
		previous = setPosition(position - 1);
		if (position < count - 1)
			following = setPosition(position + 1);
		else
			following = NULL;
	}
	else
	{
		previous = head;
		if (position < count - 1)
			following = setPosition(position + 1);
		else
			following = NULL;
	}
	previous->next = following;
	count--;
	return success;
}


链表中的其他函数的实现也比较简单,如下所示。

int list::size() const                  //返回表结构中的元素的个数
{
	return count;
}

bool list::empty() const
{
	return (count == 0);
}

void list::clear()
{
	node *old_node;
	while (head != NULL)
	{
		old_node = head;
		head = head->next;
		delete old_node;
	}
}

int list::retrieve(int position, node *x) const          //取出表位置position上的元素
{
	node *temp;
	if (empty())
		return underflow;
	if (position<1 || position>count)
	{
		cout << "位置不在合法范围内。" << endl;
		return invalid_range;
	}
	temp = setPosition(position);
	x->entry = temp->entry;
	return success;
}

int list::replace(int position, const node *x)			//用x替代表位置position上的元素
{
	if (empty())
		return underflow;
	if (position<1 || position>count)
	{
		cout << "位置不在合法范围内。" << endl;
		return invalid_range;
	}
	node *to_replace;
	to_replace = setPosition(position);
	to_replace->entry = x->entry;
	return success;
}

list::list(void)
{
	count = 0;
	head = NULL;
}

list::~list(void)
{
}

从前面实现的插入和删除操作的程序可以很容易发现,操作的成本取决于setPosition函数,而其时间复杂度为O(n),所以我们还可以进一步的优化setPosition函数操作的效率。

也就是记住当前的位置,那么下次定位就不是从头开始,而是从上次开始了。要记住当前位置,只需要堆链表类进行席位修改即可。即增加一个数据成员current,用来记录本次操作所处的位置(指向本次操作的节点),然后修改setPosition函数使其每次定位从current开始。不过需要注意的是,由于需要在必要的时候修改current,该数据成员需要被定义为一个可被修改的项。C++提供了一个mutable qualifier来让我们实现这个目的。

下面为被修改后的链表结构的类定义。

class list
{
public:
	list(void);
	int size() const;
	bool full() const;
	bool empty() const;
	void clear();
	void traverse(void(*visit)(node&));
	int retrieve(int pos, node &x) const;
	int replace(int pos, const node &x);
	int remove(int pos);
	int insert(int pos, const node &x);
	//为表结构增加的操作符重载及构析函数
	~list(void);
	list(const list &copy);
	void operator = (const list &copy);
protected:
	int count;
	node *head;
	mutable int currentPosition;
	mutable node *current;
	void setPosition(int position) const;

};

注意,这里将所有的新成员都定义为protected。这是因为要将这种链表包装得与正常的链表完全一样,这样用户在原来链表上写的程序在这个新的类下仍然适用。而因为 有了这个新数据成员currentPosition,setPosition函数就无需返回指向某个节点的指针,而只需重新设定currentPosition即可。新的setPosition函数实现如下。

void list::setPosition(int position) const    //新的setPosition函数实现
{
	if (position < currentPosition)
	{
		currentPosition = 0;
		current = head;
	}
	for (; currentPosition != position; currentPosition++)
		current = current->next;
}


双链表

为了使位置查找的更加快速,我们可以选择使用双链表,让setPosition函数能实现向左的移动,话不多说,直接上干货

#include<string>

const int SUCCESS = 0;
//const int OVERFLOW = 1;
//const int UNDERFLOW = 2;
const int INVALID_RANGE = 3;
//const int NULL = 0;

typedef char nodeEntry;

struct node
{
	nodeEntry entry[20];
	node * next;
	node * back;
};

class duLinkList
{
public:
	duLinkList();
	~duLinkList();
	int size()const;
	bool empty()const;
	void clear();
	void traverse(void(*visit)(node &));
	int retrieve(int pos, node * x)const;
	int replace(int pos, const node * x);
	int remove(int pos);
	int insert(int pos, node * x);
protected:
	int count;
	mutable int currentPosition;
	mutable node * current;
	void setPosition(int position)const;
};

void duLinkList::setPosition(int position)const					//新的setPosition函数
{
	if (position < currentPosition)								//反向查找
		for (; currentPosition != position; --currentPosition)
			current = current -> back;
	if (position > currentPosition)								//正向查找
		for (; currentPosition != position; ++currentPosition)
			current = current->next;
}

int duLinkList::insert(int position, node *x)
{
	node * new_node, *following, *preceding;
	if (position < 1)
		return INVALID_RANGE;
	if (position == 1)
	{
		if (count == 0)
			following = NULL;
		else
		{
			setPosition(position);
			following = current;
		}
		preceding = NULL;
	}
	else
	{
		setPosition(position - 1);
		preceding = current;
		following = preceding->next;
	}
	new_node = new node;
	if (new_node == NULL)
		return OVERFLOW;
	strcpy(new_node->entry, x->entry);
	new_node->back = preceding;
	new_node->next = following;
	if (preceding != NULL)
		preceding->next = new_node;
	if (following != NULL)
		following->back = new_node;
	current = new_node;
	currentPosition = position;
	++count;
	return SUCCESS;
}

int duLinkList::remove(int position)
{
	if (position < 1)
		return UNDERFLOW;
	if (position > count)
		return OVERFLOW;
	node * preceding, *follwing;
	if (position > 1)
	{
		setPosition(position - 1);
		preceding = current;
		if (position < count - 1)
		{
			setPosition(position - 1);
			follwing = current;
		}
		else
			follwing = NULL;
	}
	else
	{
		while (currentPosition != 1)
		{
			--currentPosition;
			current = current->back;
		}
		preceding = current;
		if (position < count - 1)
		{
			setPosition(position + 1);
			follwing = current;
		}
		else
		{
			follwing = NULL;
		}
	}
	preceding->next = follwing;
	--count;
	return SUCCESS;
}

int duLinkList::replace(int position, const node * x)
{
	if (empty())
		return UNDERFLOW;
	if (position<1 || position>count)
		return INVALID_RANGE;
	setPosition(position);
	strcpy(current->entry, x->entry);
	return SUCCESS;
}

duLinkList::duLinkList()
{
	count = 0;
	current = NULL;
	currentPosition = 1;
}

duLinkList::~duLinkList()
{
}

int duLinkList::size()const
{
	return count;
}

bool duLinkList::empty()const
{
	return (count == 0);
}

void duLinkList::clear()
{
	node * old_node;
	setPosition(1);
	while (current != NULL)
	{
		old_node = current;
		current = current->next;
		delete old_node;
	}
}

int duLinkList::retrieve(int position, node * x)const
{
	if (empty())
		return UNDERFLOW;
	if (position<1 || position>count)
		return INVALID_RANGE;
	setPosition(position);
	strcpy(x->entry, current->entry);
	return SUCCESS;
}

显然,双链表的节点定位时间成本只有单链表的一半^.^


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值