<C/C++数据结构>单链表

一,单链表的基本概念

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

链表中的每个节点都保存有指向下一个节点的指针,所有节点串成一条链。根据指针的不同,还有单链表、双链表和循环链表的区分,如下图所示。

单链表是只包含指向下一个节点的指针,只能单向遍历。
双链表即包含指向下一个节点的指针,也包含指向前一个节点的指针,因此可以双向遍历。
循环单链表则是将尾节点与首节点链接起来,形成了一个环状结构,在某些情况下会非常有用。
由于链表是使用指针将节点连起来,因此无需使用连续的空间,它具有以下特点:
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,链表类的设计

本类实现了
1)获取链表的长度
2)链表排序
3)创建链表环
4)判断是否有环
5)改变指定节点的值
......


// 带头结点的单链表定义 
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,本人将及时编辑掉!



  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值