[关闭]
@rg070836rg 2016-06-25T09:57:06.000000Z 字数 8778 阅读 1641

数据结构——链表报告

data_structure

返回目录


一、设计方式

结点结构
包含数据域以及指针域,数据域通过模板可以实现不同类型的数据类型。

  1. template <class T>
  2. struct Node
  3. {
  4. T data;
  5. Node<T> *next;
  6. };

链表结构
包含头结点,用于数据的存储,包含一系列函数,用于数据的访问及管理。

  1. template <class T>
  2. class LinkList
  3. {
  4. /*
  5. 略去一系列成员函数
  6. */
  7. private:
  8. Node<T> *pHead; //节点,包含数据域和指针域
  9. };

文件结构

  • LinkList.cpp
  • LinkList.h
  • test.cpp

LinkList.cpp与LinkList.h用于实现链表模板
test.cpp用于测试模板的正确性


二、具体实现

2.1 LinkList类设计

  1. template <class T>
  2. class LinkList
  3. {
  4. //**********
  5. //基本函数
  6. //**********
  7. public:
  8. LinkList(); //无参构造函数
  9. ~LinkList(); //析构函数,作垃圾回收
  10. LinkList(T a[], int n,string flag); //构造函数
  11. //**********
  12. //功能函数
  13. //**********
  14. public:
  15. int ListLength(); //返回链表长度
  16. T& Get(int pos); //按位返回元素引用
  17. int Locate(T item); //按值找位
  18. void PrintListLink(); //遍历打印
  19. void Insert(int pos, T item); //按位插值
  20. void Delete(int i); //按位删值
  21. void Invert(); //逆序链表
  22. void Merge(LinkList<T> &L); //归并链表
  23. void SelectSort(); //选择排序
  24. LinkList<T>& operator=(LinkList<T>* L);//重载赋值,便于合并
  25. friend LinkList<T>* Merge(LinkList<T> &LA, LinkList<T>&LB);
  26. friend void Merge_1(LinkList<T> &LA, LinkList<T>&LB);
  27. //**********
  28. //成员变量
  29. //**********
  30. private:
  31. Node<T> *pHead; //节点,包含数据域和指针域
  32. };

2.1.1 单链表的初始化

单链表可以有两种初始化,无参初始化,只生成头结点的空链表,有参初始化,可以实现带元素节点的链表。

单链表的无参构造函数:

  1. /**
  2. * 无参数构造函数
  3. * 作用:生成头结点
  4. * @param[in] 无
  5. * @param[out] 无
  6. */
  7. template <class T>
  8. LinkList<T>::LinkList()
  9. {
  10. pHead = new Node<T>;
  11. pHead->next = NULL;
  12. }

单链表的有参构造函数:

本函数提供三种插入方法:

  • 头插法(head)
  • 顺序插法(sorted)
  • 尾插法(tail)

根据所给标志不同,选择不同构造方法。

  1. template <class T>
  2. LinkList<T>::LinkList(T a[],int n,string flag)

1、 头插法(head)

  1. if (flag=="head")
  2. {
  3. pHead = new Node<T>;
  4. pHead->next = NULL;
  5. for (int i = 0; i < n; i++)
  6. {
  7. Node<T> *p = new Node<T>;
  8. p->data = a[i];
  9. p->next = pHead->next;
  10. pHead->next = p;
  11. }
  12. }

头插法每次生成的节点插在头结点的后面,操作简单。

2、顺序插法(sorted)

  1. else if (flag == "sorted")
  2. {
  3. pHead = new Node<T>; pHead->next = NULL;
  4. Node<T> *p, *front, *current;
  5. for (int i = 0; i < n; i++)
  6. {
  7. front = pHead;
  8. p = new Node<T>;
  9. p->data = a[i];
  10. current = pHead->next;
  11. while ((current != NULL) && (current->data < p->data))
  12. {
  13. front = current;
  14. current = current->next;
  15. }
  16. p->next = current;
  17. front->next = p;
  18. }
  19. }

顺序插法,在构造的时候,考虑顺序,使得构造函数完成的同时链表有序,时间复杂度为,p是新建节点,front以及current确定插入位置。

3、尾插法(tail)

  1. else if (flag == "tail")
  2. {
  3. pHead = new Node<T>;
  4. Node<T> * rear = pHead;
  5. for (int i = 0; i < n; i++)
  6. {
  7. Node<T> *p = new Node<T>;
  8. p->data = a[i];
  9. rear->next = p;
  10. rear = p;
  11. }
  12. rear->next = NULL;
  13. }

尾插法,每次生成的节点放在链表尾部,rear指针指向最后一个节点,无须重新遍历。便于插入操作。

  • 其他情况 程序提示错误
  1. else
  2. {
  3. cerr << "error at flag choosing!" << endl;
  4. }

2.1.2求链表长度

遍历链表,通过计数器计数,并返回。

  1. /**
  2. * int ListLength()
  3. * 作用:返回链表节点数
  4. * @param[in] 无
  5. * @param[out] int
  6. */
  7. template <class T>
  8. int LinkList<T>::ListLength()
  9. {
  10. int num = 0;
  11. Node<T> *p = pHead->next;
  12. while (p!=NULL)
  13. {
  14. p = p->next;
  15. num++;
  16. }
  17. return num;
  18. }

2.1.3按位找值

从第一个元素点开始,计数器i初始化为1,找到位序pos后,返回该位置元素的引用,注意此返回值的修改,会对链表值进行改动。同时,需要考虑异常情况,保证程序安全。

  1. /**
  2. * T& Get(int pos)
  3. * 作用:获得指定pos位的元素的引用
  4. * @param[in] pos
  5. * @param[out] T
  6. */
  7. template <class T>
  8. T& LinkList<T>::Get(int pos)
  9. {
  10. Node<T> *p = pHead->next;
  11. int i = 1;
  12. while (p!=NULL && i!=pos)
  13. {
  14. p = p->next;
  15. i++;
  16. }
  17. if (p == NULL||i>pos)
  18. {
  19. cerr << "数据非法,异常退出。";
  20. system("pause");
  21. exit(1);
  22. }
  23. else
  24. return p->data;
  25. }

2.1.4遍历单链表

遍历单链表,并输出各节点元素数据域值。

  1. /**
  2. * PrintListLink()
  3. * 作用:遍历并打印链表数据
  4. * @param[in]
  5. * @param[out]
  6. */
  7. template<class T>
  8. void LinkList<T>::PrintListLink()
  9. {
  10. Node<T> *p = pHead->next;
  11. while (p!=NULL)
  12. {
  13. cout << p->data<<"\t";
  14. p = p->next;
  15. }
  16. }

2.1.5单链表的按位插值

遍历查找到第pos-1个节点,插入元素item

  1. /**
  2. * Insert(int pos,T item)
  3. * 作用:在pos位置插入元素item
  4. * @param[in] 位置pos 元素item
  5. * @param[out]
  6. */
  7. template<class T>
  8. void LinkList<T>::Insert(int pos,T item)
  9. {
  10. Node<T> *p = pHead;
  11. int j = 0;
  12. while (p!=NULL && j<pos-1)
  13. {
  14. p = p->next;
  15. j++;
  16. }
  17. if (p == NULL)
  18. {
  19. cout << "已至链表尾,放弃插入位置,自动插入尾部";
  20. Node<T> *tmp = new Node<T>;
  21. tmp->data = item;
  22. tmp->next = NULL;
  23. p->next = tmp;
  24. }
  25. else
  26. {
  27. Node<T> *tmp = new Node<T>;
  28. tmp->data = item;
  29. tmp->next = p->next;
  30. p->next = tmp;
  31. }
  32. }

2.1.7按位删除

删除第i个元素值,必找到第i-1个节点,并使之链接到i+1个节点上, 同时销毁第i个元素的空间,同时置为NULL。

  1. /**
  2. * Delete(int i)
  3. * 作用:按照位序i删除节点
  4. * @param[in] 位置i
  5. * @param[out]
  6. */
  7. template<class T>
  8. void LinkList<T>::Delete(int i)
  9. {
  10. Node<T> *p = pHead;
  11. int j = 0;
  12. while (p!=NULL && j<i-1)
  13. {
  14. p = p->next;
  15. j++;
  16. }
  17. if (p == NULL || p->next == NULL || j > i-1 )
  18. {
  19. cerr << "删除位置不在表内,异常退出";
  20. exit(1);
  21. }
  22. else
  23. {
  24. Node<T> *tmp = p->next;
  25. p->next = tmp->next;
  26. delete tmp;
  27. tmp=NULL;
  28. }
  29. }

2.1.8 单链表的逆置

实际就是使用头插法,重新组织链表。

  1. /**
  2. * Invert()
  3. * 作用:逆序该链表
  4. * @param[in]
  5. * @param[out]
  6. */
  7. template<class T>
  8. void LinkList<T>::Invert()
  9. {
  10. Node<T> *p = pHead->next;
  11. pHead->next = NULL;
  12. while (p != NULL)
  13. {
  14. Node<T> *q = p;
  15. p = p->next;
  16. q->next = pHead->next;
  17. pHead->next= q;;
  18. }
  19. }

2.1.9 合并单链表

这块采用了三种方式合并链表:

  • 成员函数调用
  • 友元无返回值合并
  • 友元有返回值合并

下面来一一介绍。

首先,合并单链表的前提是有序,所以假定传进来的表都是有序的。这边不做有序的判断。

  • 成员函数调用

作为链表的成员函数调用,把传入的链表,归并到调用链表中。由于是成员函数,所以在其中访问私有成员没有问题。

  1. /**
  2. * Merge(LinkList<T> &L)
  3. * 作用:将L链表数据合并到调用表
  4. * @param[in]
  5. * @param[out]
  6. */
  7. template <class T>
  8. void LinkList<T>::Merge(LinkList<T> &L)
  9. {
  10. Node<T> *p3 = pHead;
  11. Node<T> *p1 = pHead->next;
  12. Node<T> *p2 = L.pHead->next;
  13. while ((p1 != NULL) && (p2 != NULL))
  14. {
  15. if ((p1->data) < (p2->data))
  16. {
  17. p3->next = p1;
  18. p1 = p1->next;
  19. p3 = p3->next;
  20. }
  21. else
  22. {
  23. p3->next = p2;
  24. p2=p2->next;
  25. p3 = p3->next;
  26. }
  27. }
  28. if (p1 != NULL) p3->next = p1;
  29. if (p2 != NULL) p3->next = p2;
  30. delete L.pHead;
  31. L.pHead = NULL;
  32. }
  • 友元无返回值合并

这是通过友元的方式,无须调用者,函数目的是把两个链表的值,归并到第一个链表中,友元的声明,使其能够方便的使用私有成员。

  1. /**
  2. * LinkList<T>* Merge(LinkList<T> &, LinkList<T>&)
  3. * 作用:通过友元方式,把LB元素合并到LA中
  4. * 注意:LA,LB需有序
  5. * @param[in] LA,LB
  6. * @param[out]
  7. */
  8. friend void Merge_1(LinkList<T> &LA, LinkList<T>&LB){
  9. Node<T> *p1 = LA.pHead->next;
  10. Node<T> *p2 = LB.pHead->next;
  11. Node<T> *p3 = LA.pHead;
  12. while (p1 != NULL && p2 != NULL)
  13. {
  14. if (p1->data<p2->data)
  15. {
  16. p3->next = p1;
  17. p1 = p1->next;
  18. p3 = p3->next;
  19. }
  20. else{
  21. p3->next = p2;
  22. p2 = p2->next;
  23. p3 = p3->next;
  24. }
  25. }
  26. if (p1 != NULL) p3 = p1->next;
  27. if (p2 != NULL) p3 = p2->next;
  28. delete LB.pHead;
  29. LB.pHead = NULL;
  30. }
  • 友元有返回值合并

这个函数与上个函数的区别在于,这个函数,合并两个链表,并返回这个合并的链表。为了接受这个返回的链表,在类中,重载了赋值运算符“=”。

  1. /**
  2. * LinkList<T>* Merge(LinkList<T> &, LinkList<T>&)
  3. * 作用:通过友元方式,合并链表LA,LB元素,并返回合并后链表
  4. * 注意:LA,LB需有序
  5. * @param[in] LA,LB
  6. * @param[out] 合并后链表LC
  7. */
  8. friend LinkList<T>* Merge(LinkList<T> &LA, LinkList<T>&LB)
  9. {
  10. LinkList<T> *LC = new LinkList<T>;
  11. Node<T> *p1 = LA.pHead->next;
  12. Node<T> *p2 = LB.pHead->next;
  13. Node<T> *p3 = LC->pHead;
  14. while (p1 != NULL && p2 != NULL)
  15. {
  16. if (p1->data < p2->data)
  17. {
  18. p3->next = p1;
  19. p1 = p1->next;
  20. p3 = p3->next;
  21. }
  22. else
  23. {
  24. p3->next = p2;
  25. p2 = p2->next;
  26. p3 = p3->next;
  27. }
  28. }
  29. if (p1 != NULL)
  30. p3->next = p1;
  31. else
  32. p3->next = p2;
  33. delete LA.pHead; LA.pHead = NULL;
  34. delete LB.pHead; LB.pHead = NULL;
  35. return LC;
  36. }

重载赋值运算符:
若链表本身为空,使用插值的方法生成链表。若本身不空,可以再次调用归并,是两者进行归并。

  1. template<class T>
  2. LinkList<T>& LinkList<T>::operator = (LinkList<T> *L)
  3. {
  4. if (this->pHead->next==NULL)
  5. {
  6. Node<T> *p = L->pHead->next;
  7. while (p != NULL)
  8. {
  9. this->Insert(this->ListLength()+1,p->data);
  10. p = p->next;
  11. }
  12. L->~LinkList();
  13. //因为尝试的是值复制,所以显示调用析构函数删除空间
  14. }
  15. else{
  16. this->Merge(*L);
  17. }
  18. return *this;
  19. }

2.1.10链表的排序

这里选用插入排序法,将链表按照升序排列。

  1. /**
  2. * SelectSort()
  3. * 作用:将L链表按升序排列
  4. * @param[in]
  5. * @param[out]
  6. */
  7. template <class T>
  8. void LinkList<T>::SelectSort()
  9. {
  10. //选择排序,每次选择未排序部分的最小值交换到前面
  11. if (pHead->next == NULL) return; //链表为空
  12. if (pHead->next->next == NULL) return; //只有一个节点
  13. Node<T> *Pre_Sorting = pHead->next; //记录正在排序的点
  14. while (Pre_Sorting != NULL)
  15. {
  16. Node<T> *pTemp = Pre_Sorting->next; //从pSorting开始往后遍历
  17. Node<T> *pMin = Pre_Sorting; //暂存从pSorting开始的最小的点
  18. while (pTemp != NULL)
  19. {
  20. if (pTemp->data < pMin->data)
  21. pMin = pTemp;
  22. pTemp = pTemp->next;
  23. }
  24. //进行交换
  25. T temp = Pre_Sorting->data;
  26. Pre_Sorting->data = pMin->data;
  27. pMin->data = temp;
  28. //排序下一个点
  29. Pre_Sorting = Pre_Sorting->next;
  30. }
  31. }

至此,链表功能基本说明完毕。下面进入测试阶段。


三、测试

3.1有参构造函数测试

部分代码:

  1. int main()
  2. {
  3. int a[10] = {8,6,2,4,5,1,3,9,7,10};
  4. LinkList<int> L1(a, 10, "head");
  5. LinkList<int> L2(a, 10, "sorted");
  6. LinkList<int> L3(a, 10, "tail");
  7. cout << endl << "链表L1(头插)为:" << endl;
  8. L1.PrintListLink();
  9. cout << endl << "链表L2(顺序插)为:" << endl;
  10. L2.PrintListLink();
  11. cout << endl << "链表L3(尾插)为:" << endl;
  12. L3.PrintListLink();
  13. ………………
  14. }

测试截图:
无

3.2返回链表长度测试

部分代码:

  1. int a[10] = { 8, 6, 2, 4, 5, 1, 3, 9, 7, 10 };
  2. LinkList<int> L(a, 10, "head");
  3. cout << endl << "链表L为:" << endl;
  4. L.PrintListLink();
  5. cout << "返回链表长度:" << endl
  6. << L.ListLength()<<endl;

测试截图:
此处输入图片的描述

3.3按位返回元素引用测试

部分代码:

  1. int a[10] = { 8, 6, 2, 4, 5, 1, 3, 9, 7, 10 };
  2. LinkList<int> L(a, 10, "head");
  3. cout << endl << "链表L为:" << endl;
  4. L.PrintListLink();
  5. cout << "返回位序5的元素值:" << endl
  6. << L.Get(5)<< endl;

测试截图:
此处输入图片的描述

3.4 按值找位测试

部分代码:

  1. int a[10] = { 8, 6, 2, 4, 5, 1, 3, 9, 7, 10 };
  2. LinkList<int> L(a, 10, "head");
  3. cout << endl << "链表L为:" << endl;
  4. L.PrintListLink();
  5. int A = 7;
  6. cout << "返回元素A(7)的位序值:" << endl
  7. << L.Locate(A) << endl;

测试截图:
此处输入图片的描述

3.5按位插值测试

部分代码:

  1. int a[10] = { 8, 6, 2, 4, 5, 1, 3, 9, 7, 10 };
  2. LinkList<int> L(a, 10, "head");
  3. cout << endl << "链表L为:" << endl;
  4. L.PrintListLink();
  5. cout<< "按位(4)插值(11):" << endl;
  6. L.Insert(4, 11);
  7. L.PrintListLink();
  8. cout << endl;

测试截图:
此处输入图片的描述

3.6按位删值测试

部分代码:

  1. int a[10] = { 8, 6, 2, 4, 5, 1, 3, 9, 7, 10 };
  2. LinkList<int> L(a, 10, "head");
  3. cout << endl << "链表L为:" << endl;
  4. L.PrintListLink();
  5. cout << "按位(1)删值:" << endl;
  6. L.Delete(1);
  7. L.PrintListLink();

测试截图:
此处输入图片的描述

3.7逆序链表测试

部分代码:

  1. int a[10] = { 8, 6, 2, 4, 5, 1, 3, 9, 7, 10 };
  2. LinkList<int> L(a, 10, "head");
  3. cout << endl << "链表L为:" << endl;
  4. L.PrintListLink();
  5. cout<< "逆序链表:" << endl;
  6. L.Invert();
  7. L.PrintListLink();

测试截图:
此处输入图片的描述

3.8 归并测试

部分代码:

  1. cout << "归并链表(成员函数):" << endl;
  2. int b[6] = { 1, 3, 5, 7, 9, 11 };
  3. int c[6] = {2,4,6,8,10,12};
  4. LinkList<int> LB(b, 6, "tail");
  5. LinkList<int> LC(c, 6, "tail");
  6. cout << endl << "链表LB为:" << endl;
  7. LB.PrintListLink();
  8. cout << endl << "链表LC为:" << endl;
  9. LC.PrintListLink();
  10. LB.Merge(LC);
  11. cout << endl << "归并后的链表LB为:" << endl;
  12. LB.PrintListLink();
  13. cout << endl;

测试截图:
此处输入图片的描述

3.9有返回值归并友元函数测试

部分代码:

  1. cout << "归并链表(有返回值函数):" << endl;
  2. int b[6] = { 1, 3, 5, 7, 9, 11 };
  3. int c[6] = {2,4,6,8,10,12};
  4. LinkList<int> LB(b, 6, "tail");
  5. LinkList<int> LC(c, 6, "tail");
  6. cout << endl << "链表LB为:" << endl;
  7. LB.PrintListLink();
  8. cout << endl << "链表LC为:" << endl;
  9. LC.PrintListLink();
  10. LinkList<int> L;
  11. cout << endl;
  12. cout << endl << "合并后的L链表:" << endl;
  13. L = Merge(LB, LC);
  14. L.PrintListLink();
  15. cout << endl;

测试截图:
此处输入图片的描述

3.10 无返回值归并链表测试

部分代码:

  1. cout << "归并链表(无返回值函数):" << endl;
  2. int b[6] = { 1, 3, 5, 7, 9, 11 };
  3. int c[6] = {2,4,6,8,10,12};
  4. LinkList<int> LB(b, 6, "tail");
  5. LinkList<int> LC(c, 6, "tail");
  6. cout << endl << "链表LB为:" << endl;
  7. LB.PrintListLink();
  8. cout << endl << "链表LC为:" << endl;
  9. LC.PrintListLink();
  10. cout << endl;
  11. cout << endl << "合并后的LB链表:" << endl;
  12. Merge_1(LB, LC);
  13. LB.PrintListLink();

测试截图:
此处输入图片的描述


                                        陈实
                                        2015年3月29日
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注