一、链表的原理
链表是线性表的链式存储方式,逻辑上相邻的数据在计算机内的存储位置不必须相邻,那么
怎么表示逻辑上的相邻关系呢?可以给每个元素附加一个指针域,指向下一个元素的存储位
置。如图所示:
从图中可以看出,每个结点包含两个域:数据域和指针域,指针域存储下一个结点的地址,
因此指针指向的类型也是结点类型
链表的核心要素:
每个节点由数据域和指针域组成
指针域指向下一个节点的内存地址
其结构体定义:
typedef struct _LinNoke
{
int data;
struct _LinNoke* next;
}LinkNode ,LinkList;
二、链表的算法实现
链表的节点均单向指向下一个节点,形成一条单向访问的数据链
单链表的初始化
typedef structLinkNode
{
int data;//结点的数据域
struct _LinkNode*next;//结点的指针域
} LinkNode,LinkList;//链表节点、链表
//构造一个空的单链表L
bool InitList (LinkList*& L)//构造一个空的单链表L
{
L = new LinkNode;//生成新结点作为头结点,用头指针L指向头结点
if( !L) return false;//生成结点失败
L->next=NULL;//头结点的指针域置空
return true;
}
前插法
bool ListInsert_front(LinkList*& L, LinkNode* node)
{
if (!L || !node)return false;
node->next = L->next;
L->next = node;
return true;
}
后插法
bool ListInsert_back(LinkList*& L, LinkNode* node)
{
LinkNode* last = NULL;
if (!L || !node)return false;
last = L;
while (last->next) last = last->next;
node->next = NULL;
last->next = node;
return true;
}
指定位置插入
bool LinkInsert(LinkList*& L, int i, int& e)
{
if (!L) return false;
int j = 0;
LinkList* p, * s;
p = L;
while (p && j < i - 1) //查找位置为i-1的结点,p指向该结点
{
p = p->next;
j++;
}
if (!p || j > i - 1)
{
return false;
}
s = new LinkNode;//生成新的节点
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
单链表的输出
void LinkPrint(LinkList*& L)
{
LinkNode* p = NULL;
if (!L)
{
cout << "链表为空" << endl;
return;
}
p = L->next;
while (p)
{
cout << p->data << "\t";
p = p->next;
}
cout << endl;
按值查找
bool Link_FindElem(LinkList* L, int e,int &index)
{
//在带头结点的单链表L中查找值为e的元素
LinkList* p;
p = L->next;
index = 1;
if (!L || !L->next)
{
index = 0;
return false;
}
while (p&&p->data !=e)
{
p = p->next;
index++;
}
if (!p)
{
index = 0;
return false;//查无此值
}
return true;
}
单链表的删除
bool LinkDelte(LinkList*& L, int i)
{
LinkList *p,*q;
int index = 0;
p = L;
if (!L || !L->next)
{
return false;
}
while ((p->next)&&(index<i-1))
{
p = p->next;
index++;
}
if (!p->next || (index > i - 1)) //当i>n或i<1时,删除位置
{
return false;
}
q = p -> next; // 临时保存被删结点的地址以备释放空间
p->next = q->next;// 改变删除结点前驱结点的指针域
delete q; //释放被删除节点的的空间
return true;
}
链表的销毁
void LinkDestroy(LinkList*& L)
{
//定义临时节点p指向头节点
LinkList* p = L;
cout << "销毁链表" << endl;
while (p)
{
L = L->next; //L指向下一个链表
cout << "删除元素" <<p->data<< endl;
delete p; //删除当前节点
p = L; //p移向下一个节点
}
}
三、函数的调用
int main()
{
LinkList* L = NULL;
LinkNode* s = NULL;
//1.初始化一个空的链表
InitList(L);
//2.使用前插法插入数据
int n;
cout << "前插法创建单链表" << endl;
std::cout << "请输入元素个数:";
cin >> n;
cout << "\n请依次输入n个元素:" << endl;
while (n>0)
{
s = new LinkNode;
cin >> s->data;
ListInsert_front(L, s);
n--;
}
//3.单链表的输出
LinkPrint(L);*/
//4.使用尾插法插入数据
int n;
cout << "尾插法创建单链表" << endl;
std::cout << "请输入元素个数:";
cin >> n;
cout << "\n请依次输入n个元素:" << endl;
while (n > 0)
{
s = new LinkNode;
cin >> s->data;
ListInsert_back(L, s);
n--;
}
LinkPrint(L);
//5.任意位置插入元素
for (int j = 0; j < 3; j++)
{
int i, x; //i位置 x是值
cout << "请输入插入的位置和元素:" << endl;
cin >> i;
cin >> x;
if (LinkInsert(L, i, x))
{
cout << "插入成功.\n";
}
else
{
cout << "插入失败\n";
}
LinkPrint(L);
}
//6.单链表根据位置获取元素
int element = 0;
if (Link_GetElem(L, 2, element))
{
cout << "获取第二个元素的值:" << element << endl;
}
else
{
cout << "获取失败" << endl;
}
//7.单链表根据值查询元素所在的位置
int index = 0;
if (Link_FindElem(L, 10, index))
{
cout << "查找元素10存在,所在位置:" <<index<< endl;
}
else
{
cout << "元素10不存在" << endl;
}
//8. 单链表删除元素
if (LinkDelte(L, 2))
{
cout << "删除第二个元素" << endl;
LinkPrint(L);
}
else
{
cout << "删除第二个元素失败" << endl;
}
//9.销毁单链表
LinkDestroy(L);
system("pause");
return 0;
}
总结
由两部分组成:数据域和指针域,每个结点都有一个指针,每个节点指针的指向都是指向自身结点的下一个结点,最后一个结点的head指向为NULL,对单链表的操作只能从一端开始,如果需要查找链表中的某一个结点,则需要从头开始进行遍历。
与双向链表相比
- 其特点是链表的链接方向是单向的,对链表的访问要通过顺序读取从头部开始。
- 单个结点创建非常方便,普通的线性内存通常在创建的时候就需要设定数据的大小,结点的访问方便,可以通过循环或者递归的方法访问到任意数据。
缺点:只能从头到尾遍历。只能找到后继,无法找到前驱,也就是只能前进。
双向链表
- 双向链表极高的灵活性,还表现在它可以封装成栈,队列。在没有随机访问的场景里,它几乎是完美的顺序结构。
- 每个节点有2个链接,一个是指向前一个节点(当此链接为第一个链接时,指向的是空值或空列表),另一个则指向后一个节点(当此链接为最后一个链接时,指向的是空值或空列表)。意思就是说双向链表有2个指针,一个是指向前一个节点的指针,另一个则指向后一个节点的指针。
优点:可以找到前驱和后继,可进可退;缺点:增加删除节点复杂