单链表,单循环链表,约瑟夫问题
2014-05-12 13:49
302 查看
线性表
线性表中的线性一词说的是一种逻辑结构,它表示除了首尾节点外,其它节点都只有一个前驱和一个后继,这是一对一的关系。要想实现线性表得考虑它的物理结构,即选用何种存储结构。线性表主要有两种存储结构:
1.顺序存储
这即是我们常用的数组,很多语言都内置了这种数据类型,可见它即基础又常用。它的特点是:按位置访问很容易,它是O(1)的;插入和删除相对比较麻烦,因为这普遍要移动元素,它是O(n)的;按内容查找对于无序的表是O(n)的。
2.链式存储,
根据链的多少可分为单链表和双链表。根据它是否首尾相连可分为循环和非循环。至于实际工作中采用何种方式,这要看解决什么问题,这里我们介绍的是单链表(LinkList),并提供它的常见操作。
它的特点是:插入和删除灵活,并且可以根据元素的多少动态的增加或减少存储空间。
注意:链式存储是最常用的数据存储方式之一,它不仅可以表示线性结构,也可以表示非线性结构。
链式存储的节点主要分两部分,一是数据域,而是链域(指针域,指向后继结点)。
画个图:
单链:
我们增加一个尾指针来方便append操作,增加一个头结点来方便特殊位置的插入和删除。
双链:
指针front指向前驱,指针rear指向后继
代码
类型声明
其中的节点类型也可用c的语法定义为
类实现
主函数
下载代码: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,直至只剩下一个节点。完成。
方法一:使用动态链
代码:http://download.csdn.net/detail/zhangxiangdavaid/7335699
在设计中我们没有用到头节点,并且在节点类型中加了一个属性flag,当flag=1时,表明此节点未出局:当flag=0时,表明节点已出局。这是常用的一技巧。
运行实例:
方法二:使用静态链
代码:
运行:
代码下载地址 http://download.csdn.net/detail/zhangxiangdavaid/7393157
至此,关于约瑟夫问题,博主已经给出了三种解法:
最常见的、最已想到的:单循环链表解法(见本文)
对第一种方法进行变化的:静态链表法(见本文)
比较有技巧的:约瑟夫问题数组解法
若是对你有所帮助,或是觉得有意思,希望顶一个哦。
专栏目录:
数据结构与算法目录
c指针
线性表中的线性一词说的是一种逻辑结构,它表示除了首尾节点外,其它节点都只有一个前驱和一个后继,这是一对一的关系。要想实现线性表得考虑它的物理结构,即选用何种存储结构。线性表主要有两种存储结构:
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指针
相关文章推荐
- 约瑟夫问题(单循环链表解决)
- 数据结构与算法(单循环链表_约瑟夫问题)
- 约瑟夫问题(Josephus问题)的递推O(n)解法、循环解法、单循环链表解法
- Have Fun with Numbers及循环链表(约瑟夫问题)
- Linux内核思想链表的系列循环链表以及求解约瑟夫问题
- JavaScript数据结构之单向循环链表应用-约瑟夫问题
- 环形链表实现约瑟夫问题 java语言
- 约瑟夫问题的单向循环链表的代码实现
- 洛谷 P1996 约瑟夫问题(链表或数组模拟)
- 数据结构(C语言):链表,约瑟夫问题
- 链表之单链表约瑟夫问题(二)
- 链表---约瑟夫问题
- 循环链表解决约瑟夫问题
- 约瑟夫问题(本人使用链表解决的)
- 链表解决约瑟夫环问题
- 环形链表+约瑟夫问题实现
- 链表 - 约瑟夫问题
- 约瑟夫问题(Josephus)链表实现
- SDUT OJ 约瑟夫问题——链表
- 约瑟夫问题——链表实现