一、设计方式
结点结构:
包含数据域以及指针域,数据域通过模板可以实现不同类型的数据类型。1
2
3
4
5
6template <class T>
struct Node
{
T data;
Node<T> *next;
};
链表结构:
包含头结点,用于数据的存储,包含一系列函数,用于数据的访问及管理。1
2
3
4
5
6
7
8
9template <class T>
class LinkList
{
/*
略去一系列成员函数
*/
private:
Node<T> *pHead; //节点,包含数据域和指针域
};
文件结构:
- LinkList.cpp
- LinkList.h
- test.cpp
LinkList.cpp与LinkList.h用于实现链表模板
test.cpp用于测试模板的正确性
二、具体实现
2.1 LinkList类设计
1 | template <class T> |
2.1.1 单链表的初始化
单链表可以有两种初始化,无参初始化,只生成头结点的空链表,有参初始化,可以实现带元素节点的链表。
单链表的无参构造函数:
1
2
3
4
5
6
7
8
9
10
11
12/**
* 无参数构造函数
* 作用:生成头结点
* @param[in] 无
* @param[out] 无
*/
template <class T>
LinkList<T>::LinkList()
{
pHead = new Node<T>;
pHead->next = NULL;
}
单链表的有参构造函数:
本函数提供三种插入方法:
- 头插法(head)
- 顺序插法(sorted)
- 尾插法(tail)
根据所给标志不同,选择不同构造方法。1
2template <class T>
LinkList<T>::LinkList(T a[],int n,string flag)
1、 头插法(head)
1 | if (flag=="head") |
头插法每次生成的节点插在头结点的后面,操作简单。
2、顺序插法(sorted)
1 | else if (flag == "sorted") |
顺序插法,在构造的时候,考虑顺序,使得构造函数完成的同时链表有序,时间复杂度为$O(n^2)$,p是新建节点,front以及current确定插入位置。
3、尾插法(tail)
1
2
3
4
5
6
7
8
9
10
11
12
13 else if (flag == "tail")
{
pHead = new Node<T>;
Node<T> * rear = pHead;
for (int i = 0; i < n; i++)
{
Node<T> *p = new Node<T>;
p->data = a[i];
rear->next = p;
rear = p;
}
rear->next = NULL;
}
尾插法,每次生成的节点放在链表尾部,rear指针指向最后一个节点,无须重新遍历。便于插入操作。
- 其他情况 程序提示错误
1
2
3
4 else
{
cerr << "error at flag choosing!" << endl;
}
###2.1.2求链表长度
遍历链表,通过计数器计数,并返回。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/**
* int ListLength()
* 作用:返回链表节点数
* @param[in] 无
* @param[out] int
*/
template <class T>
int LinkList<T>::ListLength()
{
int num = 0;
Node<T> *p = pHead->next;
while (p!=NULL)
{
p = p->next;
num++;
}
return num;
}
2.1.3按位找值
从第一个元素点开始,计数器i初始化为1,找到位序pos后,返回该位置元素的引用,注意此返回值的修改,会对链表值进行改动。同时,需要考虑异常情况,保证程序安全。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25/**
* T& Get(int pos)
* 作用:获得指定pos位的元素的引用
* @param[in] pos
* @param[out] T
*/
template <class T>
T& LinkList<T>::Get(int pos)
{
Node<T> *p = pHead->next;
int i = 1;
while (p!=NULL && i!=pos)
{
p = p->next;
i++;
}
if (p == NULL||i>pos)
{
cerr << "数据非法,异常退出。";
system("pause");
exit(1);
}
else
return p->data;
}
2.1.4遍历单链表
遍历单链表,并输出各节点元素数据域值。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/**
* PrintListLink()
* 作用:遍历并打印链表数据
* @param[in]
* @param[out]
*/
template<class T>
void LinkList<T>::PrintListLink()
{
Node<T> *p = pHead->next;
while (p!=NULL)
{
cout << p->data<<"\t";
p = p->next;
}
}
2.1.5单链表的按位插值
遍历查找到第pos-1个节点,插入元素item1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32/**
* Insert(int pos,T item)
* 作用:在pos位置插入元素item
* @param[in] 位置pos 元素item
* @param[out]
*/
template<class T>
void LinkList<T>::Insert(int pos,T item)
{
Node<T> *p = pHead;
int j = 0;
while (p!=NULL && j<pos-1)
{
p = p->next;
j++;
}
if (p == NULL)
{
cout << "已至链表尾,放弃插入位置,自动插入尾部";
Node<T> *tmp = new Node<T>;
tmp->data = item;
tmp->next = NULL;
p->next = tmp;
}
else
{
Node<T> *tmp = new Node<T>;
tmp->data = item;
tmp->next = p->next;
p->next = tmp;
}
}
2.1.7按位删除
删除第i个元素值,必找到第i-1个节点,并使之链接到i+1个节点上, 同时销毁第i个元素的空间,同时置为NULL。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29/**
* Delete(int i)
* 作用:按照位序i删除节点
* @param[in] 位置i
* @param[out]
*/
template<class T>
void LinkList<T>::Delete(int i)
{
Node<T> *p = pHead;
int j = 0;
while (p!=NULL && j<i-1)
{
p = p->next;
j++;
}
if (p == NULL || p->next == NULL || j > i-1 )
{
cerr << "删除位置不在表内,异常退出";
exit(1);
}
else
{
Node<T> *tmp = p->next;
p->next = tmp->next;
delete tmp;
tmp=NULL;
}
}
2.1.8 单链表的逆置
实际就是使用头插法,重新组织链表。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/**
* Invert()
* 作用:逆序该链表
* @param[in]
* @param[out]
*/
template<class T>
void LinkList<T>::Invert()
{
Node<T> *p = pHead->next;
pHead->next = NULL;
while (p != NULL)
{
Node<T> *q = p;
p = p->next;
q->next = pHead->next;
pHead->next= q;;
}
}
2.1.9 合并单链表
这块采用了三种方式合并链表:
- 成员函数调用
- 友元无返回值合并
- 友元有返回值合并
下面来一一介绍。
首先,合并单链表的前提是有序,所以假定传进来的表都是有序的。这边不做有序的判断。
- 成员函数调用
作为链表的成员函数调用,把传入的链表,归并到调用链表中。由于是成员函数,所以在其中访问私有成员没有问题。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32/**
* Merge(LinkList<T> &L)
* 作用:将L链表数据合并到调用表
* @param[in]
* @param[out]
*/
template <class T>
void LinkList<T>::Merge(LinkList<T> &L)
{
Node<T> *p3 = pHead;
Node<T> *p1 = pHead->next;
Node<T> *p2 = L.pHead->next;
while ((p1 != NULL) && (p2 != NULL))
{
if ((p1->data) < (p2->data))
{
p3->next = p1;
p1 = p1->next;
p3 = p3->next;
}
else
{
p3->next = p2;
p2=p2->next;
p3 = p3->next;
}
}
if (p1 != NULL) p3->next = p1;
if (p2 != NULL) p3->next = p2;
delete L.pHead;
L.pHead = NULL;
}
- 友元无返回值合并
这是通过友元的方式,无须调用者,函数目的是把两个链表的值,归并到第一个链表中,友元的声明,使其能够方便的使用私有成员。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30/**
* LinkList<T>* Merge(LinkList<T> &, LinkList<T>&)
* 作用:通过友元方式,把LB元素合并到LA中
* 注意:LA,LB需有序
* @param[in] LA,LB
* @param[out]
*/
friend void Merge_1(LinkList<T> &LA, LinkList<T>&LB){
Node<T> *p1 = LA.pHead->next;
Node<T> *p2 = LB.pHead->next;
Node<T> *p3 = LA.pHead;
while (p1 != NULL && p2 != NULL)
{
if (p1->data<p2->data)
{
p3->next = p1;
p1 = p1->next;
p3 = p3->next;
}
else{
p3->next = p2;
p2 = p2->next;
p3 = p3->next;
}
}
if (p1 != NULL) p3 = p1->next;
if (p2 != NULL) p3 = p2->next;
delete LB.pHead;
LB.pHead = NULL;
}
- 友元有返回值合并
这个函数与上个函数的区别在于,这个函数,合并两个链表,并返回这个合并的链表。为了接受这个返回的链表,在类中,重载了赋值运算符“=”。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36/**
* LinkList<T>* Merge(LinkList<T> &, LinkList<T>&)
* 作用:通过友元方式,合并链表LA,LB元素,并返回合并后链表
* 注意:LA,LB需有序
* @param[in] LA,LB
* @param[out] 合并后链表LC
*/
friend LinkList<T>* Merge(LinkList<T> &LA, LinkList<T>&LB)
{
LinkList<T> *LC = new LinkList<T>;
Node<T> *p1 = LA.pHead->next;
Node<T> *p2 = LB.pHead->next;
Node<T> *p3 = LC->pHead;
while (p1 != NULL && p2 != NULL)
{
if (p1->data < p2->data)
{
p3->next = p1;
p1 = p1->next;
p3 = p3->next;
}
else
{
p3->next = p2;
p2 = p2->next;
p3 = p3->next;
}
}
if (p1 != NULL)
p3->next = p1;
else
p3->next = p2;
delete LA.pHead; LA.pHead = NULL;
delete LB.pHead; LB.pHead = NULL;
return LC;
}
重载赋值运算符:
若链表本身为空,使用插值的方法生成链表。若本身不空,可以再次调用归并,是两者进行归并。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20template<class T>
LinkList<T>& LinkList<T>::operator = (LinkList<T> *L)
{
if (this->pHead->next==NULL)
{
Node<T> *p = L->pHead->next;
while (p != NULL)
{
this->Insert(this->ListLength()+1,p->data);
p = p->next;
}
L->~LinkList();
//因为尝试的是值复制,所以显示调用析构函数删除空间
}
else{
this->Merge(*L);
}
return *this;
}
2.1.10链表的排序
这里选用插入排序法,将链表按照升序排列。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31/**
* SelectSort()
* 作用:将L链表按升序排列
* @param[in]
* @param[out]
*/
template <class T>
void LinkList<T>::SelectSort()
{
//选择排序,每次选择未排序部分的最小值交换到前面
if (pHead->next == NULL) return; //链表为空
if (pHead->next->next == NULL) return; //只有一个节点
Node<T> *Pre_Sorting = pHead->next; //记录正在排序的点
while (Pre_Sorting != NULL)
{
Node<T> *pTemp = Pre_Sorting->next; //从pSorting开始往后遍历
Node<T> *pMin = Pre_Sorting; //暂存从pSorting开始的最小的点
while (pTemp != NULL)
{
if (pTemp->data < pMin->data)
pMin = pTemp;
pTemp = pTemp->next;
}
//进行交换
T temp = Pre_Sorting->data;
Pre_Sorting->data = pMin->data;
pMin->data = temp;
//排序下一个点
Pre_Sorting = Pre_Sorting->next;
}
}
至此,链表功能基本说明完毕。下面进入测试阶段。
三、测试
3.1有参构造函数测试
部分代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14int main()
{
int a[10] = {8,6,2,4,5,1,3,9,7,10};
LinkList<int> L1(a, 10, "head");
LinkList<int> L2(a, 10, "sorted");
LinkList<int> L3(a, 10, "tail");
cout << endl << "链表L1(头插)为:" << endl;
L1.PrintListLink();
cout << endl << "链表L2(顺序插)为:" << endl;
L2.PrintListLink();
cout << endl << "链表L3(尾插)为:" << endl;
L3.PrintListLink();
………………
}
测试截图:
3.2返回链表长度测试
部分代码:1
2
3
4
5
6int a[10] = { 8, 6, 2, 4, 5, 1, 3, 9, 7, 10 };
LinkList<int> L(a, 10, "head");
cout << endl << "链表L为:" << endl;
L.PrintListLink();
cout << "返回链表长度:" << endl
<< L.ListLength()<<endl;
测试截图:
3.3按位返回元素引用测试
部分代码:1
2
3
4
5
6 int a[10] = { 8, 6, 2, 4, 5, 1, 3, 9, 7, 10 };
LinkList<int> L(a, 10, "head");
cout << endl << "链表L为:" << endl;
L.PrintListLink();
cout << "返回位序5的元素值:" << endl
<< L.Get(5)<< endl;
测试截图:
3.4 按值找位测试
部分代码:1
2
3
4
5
6
7int a[10] = { 8, 6, 2, 4, 5, 1, 3, 9, 7, 10 };
LinkList<int> L(a, 10, "head");
cout << endl << "链表L为:" << endl;
L.PrintListLink();
int A = 7;
cout << "返回元素A(7)的位序值:" << endl
<< L.Locate(A) << endl;
测试截图:
3.5按位插值测试
部分代码:1
2
3
4
5
6
7
8 int a[10] = { 8, 6, 2, 4, 5, 1, 3, 9, 7, 10 };
LinkList<int> L(a, 10, "head");
cout << endl << "链表L为:" << endl;
L.PrintListLink();
cout<< "按位(4)插值(11):" << endl;
L.Insert(4, 11);
L.PrintListLink();
cout << endl;
测试截图:
3.6按位删值测试
部分代码:1
2
3
4
5
6
7 int a[10] = { 8, 6, 2, 4, 5, 1, 3, 9, 7, 10 };
LinkList<int> L(a, 10, "head");
cout << endl << "链表L为:" << endl;
L.PrintListLink();
cout << "按位(1)删值:" << endl;
L.Delete(1);
L.PrintListLink();
测试截图:
3.7逆序链表测试
部分代码:1
2
3
4
5
6
7int a[10] = { 8, 6, 2, 4, 5, 1, 3, 9, 7, 10 };
LinkList<int> L(a, 10, "head");
cout << endl << "链表L为:" << endl;
L.PrintListLink();
cout<< "逆序链表:" << endl;
L.Invert();
L.PrintListLink();
测试截图:
3.8 归并测试
部分代码:1
2
3
4
5
6
7
8
9
10
11
12
13cout << "归并链表(成员函数):" << endl;
int b[6] = { 1, 3, 5, 7, 9, 11 };
int c[6] = {2,4,6,8,10,12};
LinkList<int> LB(b, 6, "tail");
LinkList<int> LC(c, 6, "tail");
cout << endl << "链表LB为:" << endl;
LB.PrintListLink();
cout << endl << "链表LC为:" << endl;
LC.PrintListLink();
LB.Merge(LC);
cout << endl << "归并后的链表LB为:" << endl;
LB.PrintListLink();
cout << endl;
测试截图:
3.9有返回值归并友元函数测试
部分代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15cout << "归并链表(有返回值函数):" << endl;
int b[6] = { 1, 3, 5, 7, 9, 11 };
int c[6] = {2,4,6,8,10,12};
LinkList<int> LB(b, 6, "tail");
LinkList<int> LC(c, 6, "tail");
cout << endl << "链表LB为:" << endl;
LB.PrintListLink();
cout << endl << "链表LC为:" << endl;
LC.PrintListLink();
LinkList<int> L;
cout << endl;
cout << endl << "合并后的L链表:" << endl;
L = Merge(LB, LC);
L.PrintListLink();
cout << endl;
测试截图:
3.10 无返回值归并链表测试
部分代码:1
2
3
4
5
6
7
8
9
10
11
12
13cout << "归并链表(无返回值函数):" << endl;
int b[6] = { 1, 3, 5, 7, 9, 11 };
int c[6] = {2,4,6,8,10,12};
LinkList<int> LB(b, 6, "tail");
LinkList<int> LC(c, 6, "tail");
cout << endl << "链表LB为:" << endl;
LB.PrintListLink();
cout << endl << "链表LC为:" << endl;
LC.PrintListLink();
cout << endl;
cout << endl << "合并后的LB链表:" << endl;
Merge_1(LB, LC);
LB.PrintListLink();
测试截图:
陈实
2015年3月29日