您的位置:首页 > 其它

单链表,单循环链表,约瑟夫问题

2014-05-12 13:49 302 查看
线性表

线性表中的线性一词说的是一种逻辑结构,它表示除了首尾节点外,其它节点都只有一个前驱一个继,这是一对的关系。要想实现线性表得考虑它的物理结构,即选用何种存储结构。线性表主要有两种存储结构:

1.顺序存储

这即是我们常用的数组,很多语言都内置了这种数据类型,可见它即基础又常用。它的特点是:按位置访问很容易,它是O(1)的;插入和删除相对比较麻烦,因为这普遍要移动元素,它是O(n)的;按内容查找对于无序的表是O(n)的。

2.链式存储

根据链的多少可分为单链表双链表。根据它是否首尾相连可分为循环非循环。至于实际工作中采用何种方式,这要看解决什么问题,这里我们介绍的是单链表(LinkList),并提供它的常见操作。

它的特点是:插入和删除灵活,并且可以根据元素的多少动态的增加或减少存储空间。

注意:链式存储是最常用的数据存储方式之一,它不仅可以表示线性结构,也可以表示非线性结构。

链式存储的节点主要分两部分,一是数据域,而是链域(指针域,指向后继结点)。

画个图:

单链:



我们增加一个尾指针来方便append操作,增加一个头结点来方便特殊位置的插入和删除。

双链:

指针front指向前驱,指针rear指向后继



代码

类型声明

<span style="font-family:Courier New;font-size:18px;">
#include<iostream>
#include<iomanip>
#include<assert.h>
using namespace std;

template<class T>    //节点类
class Node
{
public:
	T data;  //数据域
	Node *next;  //链域
};

template<class T>
class LinkList    //链表类
{
private:
	Node<T> *head;
	Node<T> *tail;
	int len;  //链表长度
public:
	LinkList();  //构造函数
	LinkList(T* a, int n, int mark = 0);  //用指定数组初始化链表,但得排序,mark>0升序;mark<0降序;mark=0无序
	LinkList(LinkList<T> &list);  //复制构造函数
	~LinkList();       //析构函数
	void clearList();  //清空链表
	int size()     //求链表长度
	{return len;}
	bool empty()   //链表是否为空
	{return len == 0;}    //或者  tail==head
	bool findElem(const T &item);  //从单链表中查找元素  
	bool updateElem(const T &item, const T e);  //更新单链表中的指定元素 
	bool getElme(int pos, T &item);     //获取指定位置的元素
	bool append(const T t);      //在尾部追加元素t
	bool insert(const T t, int pos);  //在指定位置插入元素
	bool deleteAt(int pos);   //删除指定位置的元素
	void printList(int mark = 0);   //打印链表,mark>0升序;mark<0降序;mark=0无序
};</span>


其中的节点类型也可用c的语法定义为

<span style="font-family:Courier New;">
typedef int ElemType;
typedef struct node
{
	ElemType data; //数据域
	Node *next;   //链域
}Node;
</span>


类实现

<span style="font-family:Courier New;font-size:14px;">
template<class T>
LinkList<T>::LinkList()  //构造一空链表
{
	head = new Node<T>;  //创建一头结点
	head->next = NULL;
	tail = head;
	len = 0;
}
template<class T>
LinkList<T>::LinkList(T *a, int n, int mark) //用指定的数组初始化链表,但得排序,mark>0升序;mark<0降序;mark=0无序
{
	assert(a && n>0);   //防止a为空,且n非负
	len = n;
	//对数组a排序,直接插入排序
	int i = 1, j;
	if (mark)   //mark不为0,则排序
	{
		while (i < n)
		{
			j = i;
			while (j && a[j]<a[j - 1])
			{
				swap(a[j],a[j-1]);
				j--;
			}
			i++;
		}
	}
	head = new Node<T>;  //创建头结点
	tail = head;
	Node<T> *p = NULL;
	i = 0;
	while (i < n)
	{
		p = new Node<T>;
		if (mark >= 0)
			p->data = a[i];
		else
			p->data = a[n- 1- i];
		tail->next = p;
		tail = p;
		i++;
	}
	tail->next = NULL;   //这一步必须得加,否则出错!
}
template<class T>
LinkList<T>::LinkList(LinkList<T> &list)  //复制构造函数
{
	head = new Node<T>;   //创建头节点
	tail = head;
	tail->next = NULL;
	Node<T> *p = list.head->next;
	Node<T> *q = NULL;
	while (p)
	{
		q = new Node<T>;
		q->data = p->data;
		tail->next = q;
		tail = q;
		p = p->next;
		len++;
	}
	tail->next = NULL;
}
template<class T>
LinkList<T>::~LinkList()  //析构函数
{
	Node<T> *p=head, *q;
	while (p)
	{
		q = p->next;
		delete p;
		p = q;
	}
	len = 0;
}
template<class T>
void LinkList<T>::clearList()  //清空链表,只保留头节点
{
	Node<T> *p, *q;
	p = head->next;
	while (p)
	{
		q = p->next;
		delete p;
		p = q;
	}
	len = 0;
}
template<class T>
bool LinkList<T>::findElem(const T &item)  //从单链表中查找元素
{
	if (tail == head)
	{
		cerr << "链表为空!" << endl;
		return false;
	}
	Node<T> *p = head->next;
	while (p)
	{
		if (p->data == item)
			return true;
		p = p->next;
	}
	return false;
}
template<class T>
bool LinkList<T>::updateElem(const T &item, const T e)  //更新单链表中的指定元素
{
	if (tail == head)
	{
		cerr << "链表为空!" << endl;
		return false;
	}
	Node<T> *p = head->next;
	int flag = 0;
	while (p)
	{
		if (p->data == item)
		{
			p->data = e;
			flag = 1;
		}
		p = p->next;
	}
	if (flag)
		return true;
	else
		return false;
}
template<class T>
bool LinkList<T>::getElme(int pos, T &item)     //获取指定位置的元素
{
	if (tail == head || pos <= 0 || pos > len)
		return false;
	//我们把头节点的位置看作0
	int i = 1;
	Node<T> *p = head->next;
	while (i < pos)
	{
		p = p->next;
		i++;
	}
	item = p->data;
	return true;
}
template<class T>
bool LinkList<T>::append(const T t)      //在尾部追加元素t
{
	len++;
	Node<T> *p = new Node<T>;
	p->next = NULL;
	p->data = t;
	tail->next = p;
	tail = p;
	return true;
}
template<class T>
bool LinkList<T>::insert(const T t, int pos)   //在指定位置插入元素
{
	if (pos <= 0 || pos > len)
	{
		cerr << "输入的位置有错误!" << endl;
		return false;
	}
	len++;
	//找到指定位置的前一个位置
	Node<T> *p = head;   //p指向头节点
	int i = 1;
	while (i < pos)
	{
		p = p->next;
		i++;
	}
	//此时p指向指定位置的前一个位置
	Node<T> *q = new Node<T>;
	q->data = t;
	q->next = p->next;
	p->next = q;
	return true;
}
template<class T>
bool LinkList<T>::deleteAt(int pos)   //删除指定位置的元素
{
	if (tail == head)
	{
		cerr << "链表为空!" << endl;
		return false;
	}
	if (pos <= 0 || pos > len)
	{
		cerr << "输入的位置有错误!" << endl;
		return false;
	}
	len--;
	//找到指定位置的前一个位置
	Node<T> *p = head;   //p指向头节点
	int i = 1;
	while (i < pos)
	{
		p = p->next;
		i++;
	}
	if (p->next == tail)   //如果删除的是尾节点
	{
		delete tail;
		tail = p;
		tail->next = NULL;
	}
	Node<T> *q = p->next;  //q指向待删结点
	p->next = q->next;
	delete q;
	return true;
}
template<class T>
void LinkList<T>::printList(int mark=0)
{
	if (head == tail)
	{
		cerr << "链表为空!" << endl;
		return;
	}
	T *a = new T[len];
	int i = 0,j;
	Node<T> *p = head->next;
	while (p)
	{
		a[i++] = p->data;
		p = p->next;
	}
	//排序
	i = 1;
	if (mark)
	{
		while (i < len)
		{
			j = i;
			while (j && a[j]<a[j - 1])
			{
				swap(a[j], a[j - 1]);
				j--;
			}
			i++;
		}
	}
	cout.setf(ios::left);
	for (i = 0; i < len; i++)
	{
		if (mark >= 0)
			cout << setw(4) << a[i];
		else
			cout << setw(4) << a[len-1-i];
	}
	cout << endl;
	delete []a;  //释放空间
}</span>


主函数



int main()
{
	cout << "单链表操作演示" << endl;
	int i,n;
	cout << "输入链表元素个数 ";
	cin >> n;
	int *a = new int
;
	cout << "输入各个元素" << endl;
	for (i = 0; i < n; i++)
		cin >> a[i];
	int mark = 0;
	cout << "输入初始化方式 0(无序),1(递增),-1(递减):";
	cin >> mark;
	LinkList<int> list1(a, n, mark);
	cout << "输入打印链表的方式 0(无序),1(递增),-1(递减):";
	cin >> mark;
	list1.printList(mark);
	cout << endl;
	int oelem,nelem;
	cout << "更新元素:旧值 新值:";
	cin >> oelem >> nelem;
	list1.updateElem(oelem, nelem);
	cout << "打印更新后的链表:0(无序),1(递增),-1(递减):";
	cin >> mark;
	list1.printList(mark);
	cout << endl;
	cout << "在表尾追加元素:";
	cin >> nelem;
	list1.append(nelem);
	cout << "打印更新后的链表:0(无序),1(递增),-1(递减):";
	cin >> mark;
	list1.printList(mark);
	cout << endl;
	cout << "删除指定位置(1,2,3...)的元素:";
	cin >> mark;
	list1.deleteAt(mark);
	cout << "打印更新后的链表:0(无序),1(递增),-1(递减):";
	cin >> mark;
	list1.printList(mark);
	cout << endl;
	cout << "在指定位置插入元素,(位置 元素):";
	cin >> mark >> nelem;
	list1.insert(nelem, mark);
	cout << "打印更新后的链表:0(无序),1(递增),-1(递减):";
	cin >> mark;
	list1.printList(mark);
	cout << endl;
	cout << "查找元素:";
	cin >> oelem;
	list1.findElem(oelem)?cout<<"找到了!"<<endl:cout<<"没有找到!"<<endl;
	cout << endl;
	cout << "获取指定位置(1,2,3...)的元素:";
	cin >> mark;
	list1.getElme(mark, oelem) ? cout << "在位置 " << mark << " 上的元素是 " << oelem << endl:cout<<"操作失败!"<<endl;
	cout << endl;
	cout << "使用复制构造函数,创建一新链表" << endl;
	LinkList<int> list2(list1);
	cout << "打印新链表:0(无序),1(递增),-1(递减):";
	cin >> mark;
	list2.printList(mark);
	cout << endl;
	delete []a;
	system("pause");
	return 0;
}


下载代码:http://download.csdn.net/detail/zhangxiangdavaid/7335639
运行结果:



链表操作的几点总结:

头节点一般都是需要的(除非有确定的理由不要(如合适的设计),如下面要提到的约瑟夫问题),头节点的一般值域是不用的。
在对链表进行插入和删除时,我们总是找到它的前驱节点。此时的节点链域的修改是有规律的,如图:

插入时:



删除时:



cur指向待要插入(或删除)的位置,pre是其前驱

至于链表还有一些常用的形态,比如双向的,双向循环的等,弄清楚了单向的,单向循环的了,其它的就真的so easy!

下面我们尝试建立一个单向循环链表来解决约瑟夫问题,约瑟夫问题的数组解法,请看

约瑟夫问题的数组解法

由于约瑟夫问题的特点,我们很容易想到用循环链表来解决,由于目的是解决问题,我们简化数据结构的设计,没必要设计这么完整,只保留必要方法。且加了一个节点是否出局的标识设计。细节请看代码:

思路:

根据人数确定需要新建的链表节点个数,并初始化数据域为1,2,3,4……
构建一节点指针使其指向链表的第一个节点:Node *p=head;移动它,使它指向开始数的的位置。
开始移动指针p,移动m-1次(移动过程中只有flag=1的节点才计数,因为flag=0的节点表示已出局),此时p指向的位置即是出局者的位置,更改标记flag为0。移动指针p,直至指向第一个flag=1的节点,这是下一次开始数的位置。
重复步骤3,直至只剩下一个节点。完成。

方法一:使用动态链

<span style="font-family: 'Courier New'; font-size: 18px; ">
#include<stdio.h>
#include<stdlib.h>
typedef struct node
{
	int data;
	int flag;
	node *next;
}Node;
 Node *createCirList(Node *&head,int n)     //创建循环链表,n为链表大小
{
	 Node *p, *tail;
	 tail = (Node*)malloc(sizeof(Node));
	 tail->data = 1;
	 tail->flag = 1;
	 head = tail;
	 for (int i = 2; i <= n; i++)
	 {
		 p = (Node*)malloc(sizeof(Node));
		 p->data = i;
		 p->flag = 1;
		 tail->next = p;
		 tail = p;
	 }
	 tail->next = head;   //首尾相连
	 return head;
 }
 void clear(Node* head, int n)   //释放内存
 {
	 Node*q, *p;
	 p = head;
	 while (n)
	 {
		 q = p->next;
		 free(p);
		 p = q;
		 q = q->next;
		 n--;
	 }
 }
 void yuesefu(Node *head,int n,int m,int pos) //从位于pos的人开始数,第m个人出局,总共n个人
 {
	 Node *p = head;
	 int i = 1,count=1;
	 while (i < pos)    //p指向pos位置
	 {
		 p = p->next;
		 i++;
	 }
	 while (n != 1)
	 {
		 while (count < m)
		 {
			 p = p->next;
			 if (p->flag == 1)   //未出局的count才计数
			 {
				 count++;
			 }
		 }
		 //此时p指向待出局位置
		 printf("%-4d",p->data);
		 p->flag = 0;    //替换标识
		 while (p->flag==0)   //定位到下一个未出局的位置
		 p = p->next;
		 n--;
		 count = 1;   //重置
	 }
	 printf("\n");
	 printf("最后一个人是 %d\n",p->data);
 }
int main()
{
	printf("单向链表解决约瑟夫问题\n");
	int n, m, pos;
	printf("输入总人数n,起点位置pos,第几人出局m:");
	scanf_s("%d %d %d",&n,&pos,&m);
	Node* head = NULL;
	createCirList(head,n);
	printf("出局顺序是\n");
	yuesefu(head, n, m, pos);
	clear(head, n);
	system("pause");
	return 0;
}
</span>


代码:http://download.csdn.net/detail/zhangxiangdavaid/7335699

在设计中我们没有用到头节点,并且在节点类型中加了一个属性flag,当flag=1时,表明此节点未出局:当flag=0时,表明节点已出局。这是常用的一技巧。

运行实例:



方法二:使用静态链

代码:

#include<iostream>
#include<iomanip>
using namespace std;

typedef struct
{
	int id;
	int next;  //这里使用一整数行使指针功能
}Person;

int main()
{
	int n, m, s;
	cout << "输入总人数n,第s个人开始报数,数到第m的人出列:n s m ";
	cin >> n >> s >> m;
	Person *person = new Person
;   //创建代表n个人的节点
	int i;
	for (i = 0; i < n - 1; i++)  //初始化
	{
		person[i].id = i + 1;   //每个人的编号
		person[i].next = i + 1;
	}
	person[n - 1].id = n;
	person[n - 1].next = 0;    //最后一个人和第一个人相连,构成循环链表
	int count = n;     //记录链表中剩下的人数
	int k;
	if (s == 1)
		k = n - 1;
	else
		k = s - 1;
	i = 1;
	cout.setf(ios::left);
	cout << "出局顺序" << endl;
	while (count != 1)
	{
		while (i != m)
		{
			k = person[k].next;
			i++;
		}
		//此时k指向的人数到m-1,则下一个节点出局,更改当前节点的next值
		cout << setw(4) << person[person[k].next].id;
		person[k].next = person[person[k].next].next;
		count--;  //人数减一
		i = 1;
	}
	cout << endl;
	cout << "最后一人是 " << person[person[k].next].id << endl;
	delete[]person;  //释放节点空间
	system("pause");
	return 0;
}


运行:



代码下载地址 http://download.csdn.net/detail/zhangxiangdavaid/7393157

至此,关于约瑟夫问题,博主已经给出了三种解法:

最常见的、最已想到的:单循环链表解法(见本文)
对第一种方法进行变化的:静态链表法(见本文)
比较有技巧的:约瑟夫问题数组解法

若是对你有所帮助,或是觉得有意思,希望顶一个哦。

专栏目录:

数据结构与算法目录
c指针
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: