前言
顺序表是用一组地址连续的存储单元来保存数据的,所以它具有随机存取的特点。即查找快速,但是做插入或删除动作是,需要移动大量元素,效率较低。
链表是线性表的链式存储结构,它相比于顺序表,在插入和删除元素时,效率要高很多。
每个数据单元有两部分组成,一个是数据域,存储数据值;另一个是指针域,指向下一个数据单元。这样的数据单元叫做结点。
链表可分为单链表、双链表、循环链表。
基本算法
- 创建链表
顺序表存储结构的创建就是数组的初始化,即声明一个类型和大小的数组并赋值。单链表是一种松散的存储结构,动态的,同时指针域,必须为指针域赋值。创建链表的过程就是动态生成链表的过程。从空表开始,依次建立各个元素结点,逐个插入链表。
头插法:采用插队的方式,始终让新建结点在第一的位置上。类似堆栈。
假设头指针后有数据,往里插入结点即可。
LNode* pTempHead = *ppHead;
pTempHead->pNext = NULL;
for (int i= 0;i<num;i++)
{
LNode* item = new LNode();//生成新的结点
item->data = i;
//这是头插法,就是在链表顶端往下插入
item->pNext = pTempHead->pNext;
pTempHead->pNext = item;//插入到表头,新插入的结点在表头
}
数组数据显示是:4,3,2,1,0
尾插法:每次新的结点都插在终端结点的后面。
LNode* pTempHead = *ppHead;
//建立一个带头结点的单链表
for (int i= 0;i<num;i++)
{
LNode* item = new LNode();//生成新的结点
item->data = i;
pTempHead->pNext = item;//将表尾终端结点的指针指向新的结点,此时pphead中添加了结点,item在最后端
pTempHead = item;//继续让pTempHead指向ppHead的终端。
}
pTempHead->pNext = NULL;//尾插法是在这设置为null
链表数据显示:0,1,2,3,4.
- 插入结点
核心算法就是如图中
现将p的后继结点保存,再赋值给s的后继结点,就成了s->Next = p->Next 。此时将s添加到链表中:p->Next = s; 单链表的表头和表尾的特殊情况,操作时相同的。
//插入元素
void SingleList::InsertElem(LNode** ppHead,int pos,int data)
{
pLinkList pTempHead = *ppHead;//指向需要操作的数据链
if (pTempHead == NULL || pTempHead->pNext == NULL)
{
return;
}
pLinkList item;
int i = 0;
//选找到制定位置的结点,有效的可以在表尾和表头插入结点
while(pTempHead && i< pos)
{
pTempHead = pTempHead->pNext;
i++;
}
//位置不合适。有可能到了表尾此处不能为pTempHead->pNext,当为表尾是next没有分配内存,导致溢出。
if (!pTempHead ||i>pos)
{
return;
}
//进行插入操作
item = new LNode();
if (!item)
{
return;
}
item->data = data;
item->pNext = pTempHead->pNext;
pTempHead->pNext = item;
}
- 删除结点
删除结点就是将他的前级结点的指针绕过即可。实际上就一步:p->next = p->next-next;同时释放点指针。
//删除元素
void SingleList::RemoveElem(LNode** ppHead,int pos,int* getData)
{
pLinkList pTempHead = *ppHead;//指向需要操作的数据链
if (pTempHead == NULL || pTempHead->pNext == NULL)
{
return;
}
pLinkList item;
int i = 0;
//选找到制定位置的结点,此处是pTempHead->pNext,有效的能删除表尾,防止访问到表尾的next
while(pTempHead->pNext && i< pos)
{
pTempHead = pTempHead->pNext;
i++;
}
//位置不合适。有可能到了表尾,如果和插入时为ptempHead,此时item的item->next会报错
if (!pTempHead->pNext ||i>pos)
{
return;
}
item = pTempHead->pNext;
*getData = item->data;
//item->pNext = pTempHead->pNext;
pTempHead->pNext = item->pNext;
//释放资源
delete item;
}
在删除结点和插入结点时,注意循环跳出的条件,两者不一样。
代码实例
返回类型 | 函数名称 | 参数 | 功能 |
---|---|---|---|
void | InitNode | (LNode** ppHead) | 初始化结构体 |
void | CreateList | (LNode** ppHead,int num) | 创建链表 |
void | DestroyList | (LNode** ppHead) | 清空链表 |
void | InsertElem | (LNode** ppHead,int pos,int data) | 插入元素 |
void | RemoveElem | (LNode** ppHead,int pos,int* getData) | 移除元素 |
bool | IsEmptyList | (LNode* pHead) | 是否为空 |
int | GetElem | (LNode* pHead,int pos,int* data) | 获取制定位置元素 |
void | PrintList | (LNode* pHead) | 输出链表 |
void | ReverseList | (LNode* pHead,LNode ** outList) | 反转链表 |
void SingleList::InitNode(LNode** ppHead)
{
if (*ppHead)
{
DestroyList(ppHead);
}
LNode* p = new LNode();
p->data = 0;
p->pNext = NULL;
*ppHead = p;
}
//创建链表
void SingleList::CreateList(LNode** ppHead,int num)
{
InitNode(ppHead);
LNode* pTempHead = *ppHead;
//建立一个带头结点的单链表
//pTempHead = new LNode();
//pTempHead->pNext = NULL;
for (int i= 0;i<num;i++)
{
LNode* item = new LNode();//生成新的结点
item->data = i;
//这是头插法,就是在链表顶端往下插入
//item->pNext = pTempHead->pNext;
//pTempHead->pNext = item;//插入到表头,新插入的结点在表头
//这是头插法
//item->pNext = pTempHead;//将表尾终端节点的指针指向新的结点,此时item是多个点
//pTempHead = item;//将item赋值给item
pTempHead->pNext = item;
pTempHead = item;
}
pTempHead->pNext = NULL;//尾插法是在这设置为null
//*ppHead = pTempHead;
}
//销毁链表
void SingleList::DestroyList(LNode** ppHead)
{
pLinkList pTempHead = *ppHead;
//前期判断,已经为空,返回
if (pTempHead == NULL||pTempHead->pNext == NULL)
{
return;
}
pLinkList item,item1;
item = pTempHead->pNext;
while(item)//没到表尾
{
item1= item->pNext;
delete item;
item = item1;
}
pTempHead->pNext = NULL;
}
//插入元素
void SingleList::InsertElem(LNode** ppHead,int pos,int data)
{
pLinkList pTempHead = *ppHead;//指向需要操作的数据链
if (pTempHead == NULL || pTempHead->pNext == NULL)
{
return;
}
pLinkList item;
int i = 0;
//选找到制定位置的结点
while(pTempHead && i< pos)
{
pTempHead = pTempHead->pNext;
i++;
}
//位置不合适。有可能到了表尾此处不能为pTempHead->pNext,当为表尾是next没有分配内存,导致溢出。
if (!pTempHead ||i>pos)
{
return;
}
//进行插入操作
item = new LNode();
if (!item)
{
return;
}
item->data = data;
item->pNext = pTempHead->pNext;
pTempHead->pNext = item;
}
//删除元素
void SingleList::RemoveElem(LNode** ppHead,int pos,int* getData)
{
pLinkList pTempHead = *ppHead;//指向需要操作的数据链
if (pTempHead == NULL || pTempHead->pNext == NULL)
{
return;
}
pLinkList item;
int i = 0;
//选找到制定位置的结点,此处是pTempHead->pNext,有效的能删除表尾,防止访问到表尾的next
while(pTempHead->pNext && i< pos)
{
pTempHead = pTempHead->pNext;
i++;
}
//位置不合适。有可能到了表尾,如果和插入时为ptempHead,此时item的item->next会报错
if (!pTempHead->pNext ||i>pos)
{
return;
}
item = pTempHead->pNext;
*getData = item->data;
//item->pNext = pTempHead->pNext;
pTempHead->pNext = item->pNext;
//释放资源
delete item;
}
//判断单链表是否为空
bool SingleList::IsEmptyList(LNode* pHead)
{
if (NULL == pHead||NULL == pHead->pNext)
{
return true;
}
else
return false;
}
//获取单链表位置为pos的元素,从头开始找,直到第i元素为止,时间复杂度为N。
int SingleList::GetElem(LNode* pHead,int pos,int* data)
{
int j = 1;//计数器
if (pHead == NULL||pos<1)
{
return -1;
}
pLinkList item;//声明一个结点
item = pHead->pNext;//指向头结点
while(item && j<pos)
{
item =pHead->pNext;
j++;
}
*data = item->data;
return 1;
}
#pragma endregion
#pragma region 链表扩展操作
//从尾到头打印链表
void SingleList::PrintList(LNode* pHead)
{
//此处是反转打印链表,可以利用堆栈
stack<LNode*> stackList ;
pLinkList item = pHead;
while(item != NULL)
{
//压入堆栈
stackList.push(item);
item = item->pNext;
}
while(!stackList.empty())
{
//导出数据
item = stackList.top();
cout<<item->data;
stackList.pop();
}
}
void SingleList::PrintList1(LNode* pHead)
{
if (NULL == pHead || NULL == pHead->pNext) {
printf("LinkList is empty\n");
return;
}
LNode *p = pHead->pNext;
printf("LinkList:");
while (p) {
printf(" %d", p->data);
p = p->pNext;
}
printf("\n");
}
//反转链表
void SingleList::ReverseList(LNode* pHead,LNode ** outList)
{
if (pHead == NULL||pHead->pNext == NULL)
{
return;
}
pLinkList pTempHead = *outList;
pTempHead = new LNode();
pLinkList pCurrent = pHead;
while(pCurrent)
{
LNode * item = pCurrent;
//采用尾插法,创建链表即可;
item->pNext = pTempHead;
pTempHead = item;
pCurrent = pCurrent->pNext;
}
}
面试题
查找单链表中的倒数第K个结点(k > 0)
思路:使用两个指针,先让前面的指针走到正向第k个结点,这样前后两个指针的距离差是k-1,之后前后两个指针一起向前走,前面的指针走到最后一个结点时,后面指针所指结点就是倒数第k个结点。
// 查找单链表中倒数第K个结点
LNode * RGetKthNode(LNode * pHead, unsigned int k) // 函数名前面的R代表反向
{
if(k == 0 || pHead == NULL) // 这里k的计数是从1开始的,若k为0或链表为空返回NULL
return NULL;
LNode * pAhead = pHead;
LNode * pBehind = pHead;
while(k > 1 && pAhead != NULL) // 前面的指针先走到正向第k个结点
{
pAhead = pAhead->m_pNext;
k--;
}
if(k > 1 || pAhead == NULL) // 结点个数小于k,返回NULL
return NULL;
while(pAhead->m_pNext != NULL) // 前后两个指针一起向前走,直到前面的指针指向最后一个结点
{
pBehind = pBehind->m_pNext;
pAhead = pAhead->m_pNext;
}
return pBehind; // 后面的指针所指结点就是倒数第k个结点
}
查找单链表的中间结点
思路:设置两个指针,只不过这里是,两个指针同时向前走,前面的指针每次走两步,后面的指针每次走一步,前面的指针走到最后一个结点时,后面的指针所指结点就是中间结点。
这里写代码片// 获取单链表中间结点,若链表长度为n(n>0),则返回第n/2+1个结点
ListNode * GetMiddleNode(LNode * pHead)
{
if(pHead == NULL || pHead->m_pNext == NULL) // 链表为空或只有一个结点,返回头指针
return pHead;
L * pAhead = pHead;
L * pBehind = pHead;
while(pAhead->m_pNext != NULL) // 前面指针每次走两步,直到指向最后一个结点,后面指针每次走一步
{
pAhead = pAhead->m_pNext;
pBehind = pBehind->m_pNext;
//防止表尾,内存溢出.走了两步
if(pAhead->m_pNext != NULL)
pAhead = pAhead->m_pNext;
}
return pBehind; // 后面的指针所指结点即为中间结点
}
已知两个单链表pHead1 和pHead2 各自有序,把它们合并成一个链表依然有序
// 合并两个有序链表
ListNode * MergeSortedList(ListNode * pHead1, ListNode * pHead2)
{
//如果head1为空,返回head2
if(pHead1 == NULL)
return pHead2;
//如果head2为空,返回head1,这些处理防止后面访问链表中元素时,内存溢出
if(pHead2 == NULL)
return pHead1;
ListNode * pHeadMerged = NULL;
//比较大小,确定合并链表的表头指向谁。
if(pHead1->m_nKey < pHead2->m_nKey)
{
pHeadMerged = pHead1;
pHeadMerged->m_pNext = NULL;
pHead1 = pHead1->m_pNext;
}
else
{
pHeadMerged = pHead2;
pHeadMerged->m_pNext = NULL;
pHead2 = pHead2->m_pNext;
}
ListNode * pTemp = pHeadMerged;
//开始向合并链表中添加节点
while(pHead1 != NULL && pHead2 != NULL)
{
if(pHead1->m_nKey < pHead2->m_nKey)
{
//head1中元素小,通过尾插法,将小的结点先放在链表尾。
//将head1插入链表中
pTemp->m_pNext = pHead1;
//head1下移,继续循环
pHead1 = pHead1->m_pNext;
//让temp始终指向表尾,也是就next
pTemp = pTemp->m_pNext;
//表尾设置为null.
pTemp->m_pNext = NULL;
}
else
{
pTemp->m_pNext = pHead2;
pHead2 = pHead2->m_pNext;
pTemp = pTemp->m_pNext;
pTemp->m_pNext = NULL;
}
}
if(pHead1 != NULL)
pTemp->m_pNext = pHead1;
else if(pHead2 != NULL)
pTemp->m_pNext = pHead2;
return pHeadMerged;
}