一,单链表的基本概念
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
链表中的每个节点都保存有指向下一个节点的指针,所有节点串成一条链。根据指针的不同,还有单链表、双链表和循环链表的区分,如下图所示。
单链表是只包含指向下一个节点的指针,只能单向遍历。
双链表即包含指向下一个节点的指针,也包含指向前一个节点的指针,因此可以双向遍历。
循环单链表则是将尾节点与首节点链接起来,形成了一个环状结构,在某些情况下会非常有用。
由于链表是使用指针将节点连起来,因此无需使用连续的空间,它具有以下特点:
1)长度不固定,可以任意增删。
2)存储空间不连续,数据元素之间使用指针相连,每个数据元素只能访问周围的一个元素
3)存储密度小,因为每个数据元素,都需要额外存储一个指向下一元素的指针(双链表则需要两个指针)。
4)要访问特定元素,只能从链表头开始,遍历到该元素,时间复杂度为 O(n)。
5)在特定的数据元素之后插入或删除元素,不涉及到其他元素的移动,因此时间复杂度为 O(1)。
1,链表节点类的设计
template<class DataType>
class LinkNode
{
public:
LinkNode()//无参构造函数
{
next = NULL;
}
//函数参数表中的形参允许有默认值,但是带默认值的参数需要放后面
LinkNode(DataType item)
{
next = NULL;
data = item;
}
friend class LinkList<DataType>;//允许链表类任意访问节点类的私有变量
private:
DataType data;//节点数据
LinkNode<DataType> *next;//节点指针(指向下一个节点)
};
2,链表类的设计
// 带头结点的单链表定义
template<class DataType>
class LinkList
{
public:
//带有链表规模的构造函数
LinkList(int size)
{
head = new LinkNode<DataType>;//头结点
maxSize=size;//指定最大规模
nLength=0;//链表长度
}
//析构函数
~LinkList()
{
DestroyList();
}
//摧毁链表
void DestroyList();
//获取链表长度
int Length() const
{
return nLength;
}
//定位指定的位置,返回该位置上的结点指针
LinkNode<DataType>* Locate(int pos);
//在指定位置pos插入值为item的结点,失败返回false
bool Insert(DataType item, int pos);
//删除指定位置pos上的结点,item就是该结点的值,失败返回false
bool Remove(int pos);
//获取指定位置pos的结点的值,失败返回false
DataType GetData(int pos);
//更改指定位置pos的结点的值,失败返回false
bool ChangeData(int pos, DataType item);
//判断链表是否为空
bool IsEmpty() const;
//打印链表
void Print() const;
//链表排序
void SortList();
//链表逆置
void Reverse();
//创建一个链表环
void CreatCircle();
//关闭单链表环
void KillCircle();
//判断是否纯在单链表环,方法一
bool IsCircle1();
//判断是否纯在单链表环,方法二
bool IsCircle2();
private:
LinkNode<DataType> *head;//头结点
int maxSize;//允许链表最大规模
int nLength;//聊表长度
};
二,单链表C++模板实现
1,Listlink.h中各主函数代码如下:
#include "stdafx.h"
/* 单链表的结点定义 */
#include "iostream"
using namespace std;
template<class DataType>class LinkList;
template<class DataType>
class LinkNode
{
public:
LinkNode()
{
next = NULL;
}
//函数参数表中的形参允许有默认值,但是带默认值的参数需要放后面
LinkNode(DataType item)
{
next = NULL;
data = item;
}
friend class LinkList<DataType>;
private:
DataType data;
LinkNode<DataType> *next;
};
/* 返回链表中第pos个元素的地址,如果pos<0或pos超出链表最大个数返回NULL */
template<class DataType>
LinkNode<DataType>* LinkList<DataType>::Locate(int pos)
{
LinkNode<DataType> *p = head;//head和p指向共同的内容,头结点无数据,只是个指针
if (pos < 0)
{
cerr<<"位置参数有错误"<<endl;
return NULL;
}
int i = 0;
while (p != NULL && i < pos)
{
p = p->next;
i++;
}
return p;
}
template<class DataType>
bool LinkList<DataType>::Insert(DataType item, int pos)
{
if (Length() >= maxSize)
{
cout<<"错误:链表已满"<<endl;
exit(1);
}
LinkNode<DataType> *p = Locate(pos);
if (NULL == p)
return false;
LinkNode<DataType> *newNode = new LinkNode<DataType>(item);//创建新节点
if (NULL == newNode)
{
cerr << "分配内存失败!" << endl;
exit(1);
}
newNode->next = p->next;
p->next = newNode;
nLength++;
return true;
}
template<class DataType>
bool LinkList<DataType>::Remove(int pos)
{
LinkNode<DataType> *p = Locate(pos);
if (NULL == p || NULL == p->next)
return false;
LinkNode<DataType> *del = p->next;
p->next = del->next;
delete del;
nLength--;
return true;
}
template<class DataType>
void LinkList<DataType>::DestroyList()
{
LinkNode<DataType> *p = NULL;
//遍历链表,每次都删除头结点的next结点,最后保留头结点
while (NULL != head->next)
{
p = head->next;
head->next = p->next; //每次都删除头结点的next结点
delete p;
}
}
template<class DataType>
void LinkList<DataType>::Print() const
{
int count = 0;
LinkNode<DataType> *p = head;
while (NULL != p->next)
{
p = p->next;
std::cout << p->data << " ";
if (++count % 15 == 0) //每隔十个元素,换行打印
cout << std::endl;
}
}
template<class DataType>
void LinkList<DataType>::Reverse()
{
LinkNode<DataType> *preNode = head->next;
LinkNode<DataType> *curNode = preNode->next;
LinkNode<DataType> *next = NULL;
head->next->next = NULL;
while (curNode)
{
next = curNode->next;
curNode->next = preNode;
preNode = curNode;
curNode = next;
}
head->next = preNode;
}
//判断链表是否为空
template<class DataType>
bool LinkList<DataType>::IsEmpty() const
{
if (Length()==0)
{
return true;
}
else
{
return false;
}
}
//更改指定位置pos的结点的值,失败返回false
template<class DataType>
bool LinkList<DataType>::ChangeData(int pos, DataType item)
{
LinkNode<DataType> *p=Locate(pos);
if (pos < 0||pos>=Length())
{
cout<<"位置参数有错误"<<endl;
return false;
}
p->data=item;
return true;
}
template<class DataType>
DataType LinkList<DataType>::GetData(int pos)
{
LinkNode<DataType> *p=Locate(pos);
return p->data;
}
//链表排序
template<class DataType>
void LinkList<DataType>:: SortList()
{
for (int i=1;i<Length();i++)
{
LinkNode<DataType> *curNode=Locate(i);
for (int j=i+1;j<Length()+1;j++)
{
LinkNode<DataType> *afterNode=Locate(j);
if (afterNode->data>curNode->data)
{
DataType temp;
temp=curNode->data;
curNode->data=afterNode->data;
afterNode->data=temp;
}
}
}
}
//创建一个链表环
template<class DataType>
void LinkList<DataType>:: CreatCircle()
{
int nLen=Length();
int nLen1=nLen/2-1;
LinkNode<DataType> *ptail=Locate(nLen-1);
LinkNode<DataType> *pcirStart=Locate(nLen1);
ptail->next=pcirStart;
}
//关闭链表环
template<class DataType>
void LinkList<DataType>:: KillCircle()
{
int nLen=Length();
LinkNode<DataType> *ptail=Locate(nLen-1);
ptail->next=NULL;
}
//是否纯在链表环?方法1
template<class DataType>
bool LinkList<DataType>::IsCircle1()
{
int nLen=Length();
LinkNode<DataType> *ptail=Locate(nLen-1);
if (ptail->next!=NULL)
{
return true;
}
return false;
}
//是否纯在链表环?方法2
template<class DataType>
bool LinkList<DataType>::IsCircle2()
{
if ( head ==NULL)
{
cerr<<"空链表"<<endl;
exit(1);
}
LinkNode<DataType> *pFast,*pSlow;
pSlow=head;
pFast=head;
while(pFast!=NULL&&pFast->next!=NULL)
{
pFast=pFast->next->next;
pSlow=pSlow->next;
if (pSlow==pFast)
{
return true;
break;
}
}
return false;
}
2,主测试函数
// ConsoleAppLinklist.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
#include "LinkList.h"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
LinkList<char> A(10);
char srcStr='A';
for (int i=0;i<10;i++)
{
A.Insert(srcStr++,i);//i=0,产生第一个节点,而不是头结点
}
cout<<"初始化的链表为:"<<endl;
A.Print();
cout<<" 共"<<A.Length()<<"个"<<endl;
cout<<"链表反序:"<<endl;
A.Reverse();
A.Print();
cout<<endl;
int nPos=0;
char ans='n';
do
{
cout<<"删除哪个位置的元素?(从零开始算)"<<endl;
cin>>nPos;
bool flag=false;
do
{
if (nPos>=A.Length())
{
cerr<<"参数过大,请重新输入"<<endl;
cin>>nPos;
flag=true;
}else
{
flag=false;
}
} while (flag);
A.Remove(nPos);
cout<<"已删除,是否继续执行该操作(y/n)?"<<endl;
cin>>ans;
} while (ans=='y'||ans=='Y');
cout<<"删除后的链表为:"<<endl;
A.Print();
cout<<" 共"<<A.Length()<<"个"<<endl;
if (A.IsEmpty())
{
cout<<"链表状态:已经空"<<endl;
}else
{
cout<<"链表状态:不为空"<<endl;
}
cout<<"链表反序:"<<endl;
A.Reverse();
A.Print();
cout<<endl;
int nPos1=0;
char data='0';
cout<<"请输入您要更改的链表位置以及值(例如:3 D)"<<endl;
cin>>nPos1>>data;
A.ChangeData(nPos1,data);
A.Print();
cout<<endl;
cout<<"获取第五个位置的结点值:"<<A.GetData(5)<<endl;
cout<<"链表的排序:"<<endl;
A.SortList();
A.Print();
//cout<<endl;
A.CreatCircle();
cout<<endl;
//A.Print();
bool flag1=A.IsCircle1();
if (flag1)
{
cout<<"请注意:存在单链表环(方法1)"<<endl;
}
else
{
cout<<"不存在单链表环(方法1)"<<endl;
}
bool flag2=A.IsCircle2();
if (flag2)
{
cout<<"请注意:存在单链表环(方法2)"<<endl;
}
else
{
cout<<"不存在单链表环(方法2)"<<endl;
}
cout<<"关闭链表环后的测试:"<<endl;
A.KillCircle();
bool flag3=A.IsCircle1();
if (flag3)
{
cout<<"请注意:存在单链表环(方法1)"<<endl;
}
else
{
cout<<"不存在单链表环(方法1)"<<endl;
}
bool flag4=A.IsCircle2();
if (flag4)
{
cout<<"请注意:存在单链表环(方法2)"<<endl;
}
else
{
cout<<"不存在单链表环(方法2)"<<endl;
}
A.DestroyList();
cout<<"链表已经摧毁"<<endl;
system("pause");
return 0;
}
3,测试结果:
三,单链表小试牛刀
面试题一,链表中倒数第k个结点
-
题目描述:
-
输入一个链表,输出该链表中倒数第k个结点。(hint: 请务必使用链表。)
-
输入:
-
输入可能包含多个测试样例,输入以EOF结束。对于每个测试案例,输入的第一行为两个整数n和k(0<=n<=1000, 0<=k<=1000):n代表将要输入的链表元素的个数,k代表要查询倒数第几个的元素。输入的第二行包括n个数t(1<=t<=1000000):代表链表中的元素。
-
输出:
-
对应每个测试案例,若有结果,输出相应的查找结果。否则,输出NULL。
-
样例输入:
-
5 2 1 2 3 4 5 1 0 5
-
样例输出:
-
4 NULL
#include "vector"
#include "string"
#include "algorithm"
#include <iostream>
#include "stack"
#include <cmath>
using namespace std;
//节点定义
class LinkNode
{
public:
LinkNode(int item)//有参数的构造
{//函数参数表中的形参允许有默认值,但是带默认值的参数需要放后面
next = NULL;
data = item;
}
friend class LinkList;//允许链表类随意访问节点数据
private:
int data;
LinkNode *next;
};
// 带头结点的单链表定义
class LinkList
{
public:
LinkList()
{
head = new LinkNode(0);//头结点,并未该节点赋值0
nLength = 0;
}
~LinkList(){}
//定位指定的位置,返回该位置上的结点指针
LinkNode* Locate(int pos);
//在指定位置pos插入值为item的结点,失败返回false
bool Insert(int item, int pos);
//打印倒数第k个链表节点
void Print(int k);
private:
LinkNode *head;//头结点指针
int nLength;//统计节点长度,不算头结点
};
//返回链表中第pos个元素的地址,第0个元素是头结点
LinkNode* LinkList::Locate(int pos)
{
LinkNode *p = head;
int i = 0;
while (p != NULL && i < pos)//p==NULL说明是末尾了
{
p = p->next;
i++;
}
return p;
}
//在pos位置的节点后面插入新节点并赋值item
bool LinkList::Insert(int item, int pos)
{
LinkNode *p = Locate(pos);
LinkNode *newNode = new LinkNode(item);//创建新节点,该节点值为item
//建立连接,注意新节点是插在pos位置的后面
newNode->next = p->next;
p->next = newNode;
nLength++;
return true;
}
void LinkList::Print(int k)
{
LinkNode *p = Locate(this->nLength - k + 1);//第零个节点是头结点,不用输出
cout << p->data << endl;
}
int main()
{
int n = 0, k = 0, val = 0;
while (cin>>n>>k)
{
if (n > 0)
{
LinkList list;
for (int i = 0; i < n; i++)
{
cin >> val;
list.Insert(val, i);//在第i个位置之后插入值为val的节点
}
if (k == 0 || k > n)
{
cout << "NULL" << endl;
continue;
}
list.Print(k);
}
else
{
cout << "NULL" << endl;
continue;
}
}
return 0;
}
面试题2,反转链表
-
题目描述:
-
输入一个链表,反转链表后,输出链表的所有元素。
(hint : 请务必使用链表)
-
输入:
-
输入可能包含多个测试样例,输入以EOF结束。
对于每个测试案例,输入的第一行为一个整数n(0<=n<=1000):代表将要输入的链表的个数。
输入的第二行包含n个整数t(0<=t<=1000000):代表链表元素。
-
输出:
-
对应每个测试案例,
以此输出链表反转后的元素,如没有元素则输出NULL。
-
样例输入:
-
5 1 2 3 4 5 0
-
样例输出:
-
5 4 3 2 1 NULL
#include "iostream"
using namespace std;
//节点定义
class LinkNode
{
public:
LinkNode(int item)//有参数的构造
{//函数参数表中的形参允许有默认值,但是带默认值的参数需要放后面
next = NULL;
data = item;
}
friend class LinkList;//允许链表类随意访问节点数据
private:
int data;
LinkNode *next;
};
// 带头结点的单链表定义
class LinkList
{
public:
//有参数的构造函数
LinkList(int size)
{
head = new LinkNode(0);//头结点,并未该节点赋值0
nLength = 0;
}
~LinkList(){}
//定位指定的位置,返回该位置上的结点指针
LinkNode* Locate(int pos);
//在指定位置pos插入值为item的结点,失败返回false
bool Insert(int item, int pos);
//反转链表
void Reverse(int len);
private:
LinkNode *head;//头结点指针
int nLength;//统计节点长度,不算头结点
};
//返回链表中第pos个元素的地址,第0个元素是头结点
LinkNode* LinkList::Locate(int pos)
{
LinkNode *p = head;
int i = 0;
while (p != NULL && i < pos)//p==NULL说明是末尾了
{
p = p->next;
i++;
}
return p;
}
//在pos位置的节点后面插入新节点并赋值item
bool LinkList::Insert(int item, int pos)
{
LinkNode *p = Locate(pos);
LinkNode *newNode = new LinkNode(item);//创建新节点,该节点值为item
//建立连接
newNode->next = p->next;
p->next = newNode;
nLength++;
return true;
}
void LinkList::Reverse(int len)
{
for (size_t i = 0; i < len; i++)
{
LinkNode *p = Locate(len-i);
if (i==0)
cout << p->data;
else
cout <<" "<< p->data;
}
cout << endl;
}
int main()
{
int n = 0, val = 0;
while (cin>>n)
{
if (n == 0)
{
cout << "NULL" << endl;
continue;
}
LinkList s(n);
for (int i = 0; i < n; i++)
{
cin >> val;
s.Insert(val, i);//在第i个位置插入值为val的节点
}
s.Reverse(n);
}
return 0;
}
参考资源:
【1】《算法导论》
【2】《维基百科》
【3】http://www.cnblogs.com/scandy-yuan/archive/2013/01/06/2847801.html
【4】http://www.cppblog.com/cxiaojia/archive/2012/07/31/185760.html
【5】《STL源码剥析》,侯捷著
【6】《C++妙趣横生的算法》,胡浩著
【7】九度OJ,http://ac.jobdu.com/problemset.php?search=二叉树
注:
本文部分文字学习并copy自网络,代码参考并改编自《算法导论》.
如果侵犯了您的版权,请联系本人tangyibiao520@163.com,本人将及时编辑掉!