文章目录
1 链表
1.1 单链表
不带头节点的单链表:
带头节点的单链表: 区别就是头节点的data不保存信息。
1.1.1 单链表及其节点
链表是一系列的存储数据元素的单元通过指针串接起来形成的,因此每个单元至少有两个域,一个域用于数据元素的存储,另一个域是指向其他单元的指针。
这里具有一个数据域和多个指针域的存储单元通常称为 节点(node)。节点结构:
┌───┬───┐
│data │next │
└───┴───┘
data域–存放节点值的数据域;
next域–存放节点的直接后继的地址(位置)的指针域(链域)。
1.1.2 单链表的创建
头插法:
单链表是用户不断申请存储单元和改变链接关系而得到的一种特殊数据结构,将链表的左边称为链头,右边称为链尾。头插法建单链表是将链表右端看成固定的,链表不断向左延伸而得到的。头插法最先得到的是尾节点。
尾插法:
若将链表的左端固定,链表不断向右延伸,这种建立链表的方法称为尾插法。尾插法建立链表时,头指针固定不动,故必须设立一个搜索指针,向链表右边延伸,则整个算法中应设立三个链表指针,即头指针head、搜索指针p2、申请单元指针pl。尾插法最先得到的是头节点。
尾插图解:
1.1.3 带头节点的链表和不带头结点的链表有何不同
- 所有的链表都有一个头指针head,带头结点的链表中head的数据项为空。带头节点的单链表,不论删除和插入的位置如何,不需要修改head的值。 单链表一般为带头结点的单链表。不带头结点的单链表则需要修改head的值。
- 带头节点的链表的插入:首先假设临时变量p等于要插入之前的节点(不管具体的插入位置),之后不管要插入的节点x是插到链表头还是插到链表的其他位置都是如下语句:先让x->next指向p的下一个节点,然后再把x的值赋值给p->next。
x->next = p->next;
p->next = x;
- 不带头结点的链表的插入
若要插到链表的开头则
x->next = pHead;//新节点的下一个指向头(这个pHead就是指向第一个节点处的指针)
pHead = x;
若插到链表的其他位置则
p = 插入之前的节点
x->next = p->next;
p->next = x;
(有点类似于带头结点的插入)
1.1.4 单链表的建立具体代码:头插和尾插(C代码,不带头结点的单链表)
#include<stdio.h>
#include<stdlib.h>
//单链表结构体基本单位定义,size代表大小
int size;
struct ListNode
{
int value;
ListNode *next;
};
//不带头结点创建单向链表,尾插法
ListNode* CreatListNode_end(int n)
{
if(n <= 0)
{
printf("元素个数必须大等于1,输入不合法,创建失败!\n");
return 0;
}
ListNode *phead,*pend,*pnew;
phead = pend = NULL;
for(int i = 0;i<n;i++)
{
pnew = (ListNode*)malloc(sizeof(ListNode));
scanf("%d",&pnew->value);
if(i==0)//每回都得判断是否为头结点
phead = pnew;
else
pend->next = pnew;
pnew->next = NULL;
pend = pnew;//尾插法,所以每次插入之后最后一个节点就是新节点
size++;
}
return phead;
}
//不带头结点创建单向链表,头插法
ListNode* CreatListNode_head(int n)
{
if(n<=0)
{
printf("元素个数必须大等于1,输入不合法,创建失败!\n");
return 0;
}
ListNode *phead,*pend,*pnew; //头指针,上一节点指针,新节点指针
phead = NULL;
int i;
for(i=0;i<n;i++)
{
pnew = (ListNode*)malloc(sizeof(ListNode));
scanf("%d",&pnew->value);
pnew->next = phead;
phead = pnew;//头插法,每次结束都得到头结点
size++;
}
return phead;
}
//显示函数,打印全部
void Print_all_ListNode(ListNode *phead)
{
ListNode *p = phead;
while(p!= NULL)
{
printf("%d ",p->value);
p = p->next;
}
printf("\n");
}
int main()
{
//尾插法测试
printf("tail_insert:\n");
ListNode* pHead_tail = CreatListNode_end(10);
Print_all_ListNode(pHead_tail);
//头插法测试
printf("head_insert:\n");
ListNode* pHead_head = CreatListNode_head(10);
Print_all_ListNode(pHead_head);
return 0;
}
1.1.5 节点结构体【之后的例子都是带头结点的例子,即pHead是一个指向指针的指针】
//节点结构体
struct ListNode
{
int data;
struct ListNode* pNext;
};
1.1.6 链表的初始化
void SListInit(ListNode **p_First)
{
assert(p_First != NULL);
**p_First =NULL;
}
1.1.7 打印链表
用for遍历:
void SListPrint(ListNode *First)
{
for(ListNode*cur = First; cur != NULL; cur = cur->pNext)
{
printf("%d-->", cur->data);
}
printf("\n");
return 0;
}
或者传统while遍历:
Linklist *display(Linklist *head) //打印链表数据
{
Linklist *p;
p=head->next;
if(p==NULL)
{
printf("linklist is empty...\n");
// return;
}
while(p!=NULL)
{
printf("%5d",p->data);
p=p->next;
}
printf("\n");
return head;
}
1.1.8 往链表的尾部添加一个节点
//节点节构体
struct ListNode
{
int data;
struct ListNode* pNext;
};
//尾部添加一个节点
void AddToTail(ListNode **pHead, int value)
{
ListNode* pNew = new ListNode();//创建节点
//节点数据赋值
pNew->data = value;
pNew->pNext = nullptr;
if(*pHead == nullptr)//链表为空直接将头节点指向该新值即可
{
*pHead = pNew;
}
else//否则找到尾节点,让尾节点指向该新值
{
ListNode* pNode = *pHead;
while(pNode->pNext != nullptr)
{
pNode = pNode->pNext;
}
pNode->pNext = pNew;
}
}
1.1.9 在链表中找到第一个含有某值的节点并删除该节点
void RemoveNode(ListNode** pHead, int value)
{
//特殊处理
if(pHead == nullptr || *pHead == nullptr)
return;
//处理
ListNode* pToBeDeleted == nullptr;
if((*pHead)->data == value)//若为头指针的话
{
pTobeDeleted = *pHead;//把头指针赋值给要删除的值
*pHead = (*pHead)->data;//并让头指针指向下一个数
}
else//若不是头指针的话,就要寻找这个值
{
ListNode* pNode = *pHead;
while(pNode->pNext != nullptr|| pNode->pNext->data != value)
{
pNode = pNode->pNext;
}
if(pNode->pNext != nullptr|| pNode->pNext->data == value)
{
pToBeDeleted = pNode->pNext;
pNode->pNext = pNode->pNext->pNext;
}
}
if(pToBeDeleted != nullptr)
{
delete pToBeDeleted;
pToBeDeleted = nullptr;
}
}
2 面试题6:从尾到头打印链表
题目描述: 输入一个链表的头节点,从尾到头反过来打印出每个节点的值,不允许修改输入数据(不能改变链表的节构)。链表节点定义如下:
struct ListNode()
{
int m_nKey;
ListNode* m_pNext;
}
分析: 链表中第一个遍历到的节点是最后一个输出的,最后遍历到的节点是先输出的,所以是“后进先出”。我们可以用栈来实现这种顺序,每经过一个节点就把该节点放到一个栈中,等遍历完链表之后,就从栈顶开始输出节点的值。
2.1 从尾到头打印链表内容代码:
struct ListNode
{
int m_nKey;
ListNode* m_pNext;
};
void PrintListReversingly(ListNode* pHead)
{
std::stack<ListNode*>nodes;//定义栈
ListNode* pNode = pHead;//pNode 指向链表的头
//遍历链表将其放入栈中
while(pNode != nullptr)
{
nodes.push(pNode);
pNode = pNode->m_pNext;
}
//从栈中依次将输出栈顶的值,即倒序输出链表中的值,并将其出栈
while(!nodes.empty())
{
pNode = nodes.top();//pNode 不停的指向栈顶的数据
printf("%d\t", pNode->m_nValue);
nodes.pop();
}
}
2.2 输入链表,将链表内容从尾到头的顺序以一个结果数组的形式返回:
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) :
* val(x), next(NULL) {
* }//定义结构体函数
* };
*/
class Solution {
public:
vector<int> printListFromTailToHead(ListNode* head)
{
vector <int> result;//定义结果向量
stack<int> arr;//定义栈
ListNode* p=head;
//链表元素入栈
while(p!=NULL)
{
arr.push(p->val);
p=p->next;
}
//栈的元素依次放入结果数组
int len=arr.size();
for(int i=0;i<len;i++)
{
result.push_back(arr.top()); //向结果数组中添加元素
arr.pop();//添加一个出一个栈
}
return result;//返回结果数组
}
};