数据结构中按逻辑结构分为4类:集合、线性表、树、图。按存储结构分为2类:顺序存储、链式存储。
一些辅助定义:
#define MAXSIZE 20
#define OK 0
#define ERROR 1
typedef int Status;
typedef int ElemType;
//顺序存储
typedef struct
{
ElemType data[MAXSIZE];
int length;
}SqList;
//链式存储节点
typedef struct Node
{
ElemType data;
struct Node *next;
}Node,*PNode;
//静态链式线性表
typedef struct {
ElemType data;
int cur;
}Component,StaticLinkList[MAXSIZE];
所有代码都是可运行的,为方便测试,现附上main()函数。
int main(int argc, const char * argv[])
{
PNode pNode = NULL;
PNode _pNode = NULL;
Status ret = ERROR;
ret = CreatLinkList(&pNode, MAXSIZE);
if (ret) {
printf("创建链表失败!\n");
return ERROR;
}
ret = CreatLinkList(&_pNode, MAXSIZE);
if (ret) {
printf("创建链表失败!\n");
return ERROR;
}
// PrintLinkList(pNode);
// ReverseLinkList(&pNode);
// PrintLinkList(_pNode);
// UnionLinkList(&pNode, _pNode);
// PrintLinkList(pNode);
PNode entryNode = NULL;
PNode *mtpNode = (PNode *)malloc(sizeof(Node));
ret = IsCircleLinkList(pNode, mtpNode);
if (!ret) {
printf("该链表无环!\n");
}else{
entryNode = FindEntryNode(pNode, (*mtpNode));
printf("该链表有环!\n环节点的值为:%d\n",entryNode->data);
}
return 0;
}
1.线性表的顺序存储
元素插入
Status ListInsert(SqList *L, int i, ElemType e)
{
i -= 1;
if (L == NULL || L->length<i || i < 0 || L->length >= MAXSIZE) {
printf("链表为空,或者插入位置非法!\n");
return ERROR;
}
for (int j = L->length-1; j >= i; j--) {
L->data[j+1] = L->data[j];
}
L->data[i] = e;
L->length += 1;
return OK;
}
Status ListDelete(SqList *L, int i, ElemType *e)
{
if (L == NULL || i < 1 || i > L->length) {
printf("链表为空,或者删除位置溢出!");
return ERROR;
}
*e = L->data[i -1];
for (int j = i; j < L->length; j++) {
L->data[j-1] = L->data[j];
}
L->length -= 1;
return OK;
}
2.线性表的链式存储(有两种实现方式(动态链表/静态链表),其中静态链表用数组实现,数组下标相当于动态链表中的指针域)
//动态链表
创建链表
Status CreatLinkList(PNode *ppNode, int n)
{
if (n < 1) {
printf("参数错误!\n");
return ERROR;
}
*ppNode = (PNode)malloc(sizeof(Node));//头结点
if (!(*ppNode)) {
printf("内存分配失败!\n");
return ERROR;
}
(*ppNode)->data = 0;
(*ppNode)->next = NULL;
int i = 0;
PNode _ppNode = NULL;
PNode head_pNode = (*ppNode);
for (i = 0; i < n; i++) {
_ppNode = (PNode)malloc(sizeof(Node));
if (_ppNode) {
_ppNode->data = i;
_ppNode->next = NULL;
head_pNode->next = _ppNode;
head_pNode = head_pNode->next;
}else{
printf("内存分配失败!\n");
return ERROR;
}
}
(*ppNode)->data = i;
return OK;
}
void PrintLinkList(PNode pNode)
{
PNode _ppNode = pNode->next;
if (!pNode || !_ppNode) {
printf("链表为空!\n");
return;
}
printf("打印链表:\n");
while (_ppNode) {
printf("%d\t",_ppNode->data);
_ppNode = _ppNode->next;
}
printf("\n打印结束\n");
}
链表逆序(这个应该用前插法,我这写法太笨了,大家就不要看了。)
void ReverseLinkList(PNode *ppNode)
{
if (!ppNode || !(*ppNode)) {
printf("链表为空!\n");
return;
}
PNode left_pNode = NULL;//反转链表用
PNode right_pNode = NULL;
PNode _ppNode = (*ppNode)->next;
while (_ppNode) {
right_pNode = _ppNode->next;
_ppNode->next = left_pNode;
left_pNode = _ppNode;
_ppNode = right_pNode;
}
(*ppNode)->next = left_pNode;
}
void UnionLinkList(PNode *ppNode, PNode pNode)
{
if (!ppNode || !(*ppNode) || !pNode) {
printf("链表非法!\n");
return;
}
PNode tmp_pNode = pNode->next;//保存从pNode中取出的节点
PNode pri_pNode = (*ppNode);//保存当前遍历节点的前一个节点
PNode cur_pNode = pri_pNode->next;//保存当前节点
pNode = pNode->next;
while (tmp_pNode && cur_pNode)
{
while (cur_pNode)
{
if (tmp_pNode->data <= cur_pNode->data)
{
pNode = pNode->next;
tmp_pNode->next = cur_pNode;
pri_pNode->next = tmp_pNode;
tmp_pNode = pNode;
break;
}
else
{
pri_pNode = cur_pNode;
cur_pNode = cur_pNode->next;
}
}
}
if (tmp_pNode && !cur_pNode) {
pri_pNode->next = tmp_pNode;
}
}
判断链表是否存在环
原理:通过快慢指针实现。设慢指针slow_pNode(一次走一步),快指针fast_pNode(一次走两步)。 假设当慢指针刚进入换的时候,快指针已近在环中走过了m步。则,当慢指针在环中走过 i 步时,快指针走过了m+2 * i步,此时快指针比慢指针多走了step = m + 2*i - i = m + i步。一定存在一个 i 值,使得step = n(环的长度)。代码如下:
Status IsCircleLinkList(PNode pNode, PNode *mtpNode)
{
if (!pNode){
printf("链表为空!\n");
return ERROR;
}
PNode slow_pNode = pNode;
PNode fast_pNode = pNode;
while (fast_pNode)
{
if (!fast_pNode->next) {
return OK;
}
fast_pNode = fast_pNode->next->next;
slow_pNode = slow_pNode->next;
if (fast_pNode == slow_pNode)//有环
{
(*mtpNode) = fast_pNode;//把相遇节点传出来,便于寻找入口节点。
return ERROR;
}
else if (!fast_pNode)
{
return OK;
}
}
return OK;
}
寻找入口节点:
原理:以相遇节点为标识,在逻辑上将有环链表分成两个单链表pNode 和 _pNode。然后计算两个链表长度的差step,让较长链表先走step步,之后两个链表挨个节点进行比较,直至找到相同节点,即为入口节点(该原理类似从两个不同的单链表中找出其相同的节点)。
图例如下:(最后的节点9不是指向NULL,而是指向前面的节点5)
1-->2-->3-->4--5-->6-->7-->8-->9
^ |
|____________|
此时,假设相遇节点为7,以7为标示,将该链表从逻辑上分为以下两个链表:
1-->2-->3-->4--5>--6>-->7
7-->8-->9-->5-->6
实现代码:(pNode相当于上面的1节点地址,_pNode相当于7节点地址)
PNode FindEntryNode(PNode pNode, PNode _pNode)
{
int len1 = 0, len2 = 0;
PNode tmp_pNode = pNode->next;
while (tmp_pNode != _pNode) {
++len1;
tmp_pNode = tmp_pNode->next;
}
len1 += 1;
tmp_pNode = _pNode->next;
while (tmp_pNode != _pNode) {
++len2;
tmp_pNode = tmp_pNode->next;
}
len2 += 1;
int step = abs(len1 - len2);
while (step) {
pNode = pNode->next->next;
step--;
}
tmp_pNode = _pNode;//由环解开的链表
while (pNode != tmp_pNode) {//没走到虚拟的链表尾部,实际就是从相遇节点把链表分成两个假象的单链表
if(pNode == _pNode){
break;
}
pNode = pNode->next;
_pNode = _pNode->next;
}
return pNode;
}
//静态链表
实现原理如图:
初始化链表
Status InitStaticLinkList(StaticLinkList space)
{
int i;
for (i = 0; i < MAXSIZE - 1; i++) {
space[i].cur = i+1;
}
space[MAXSIZE -1].cur = 0;
return OK;
}
int Malloc(StaticLinkList space)
{
if (space == NULL) {
return 0;
}
int i = space[0].cur;
if (i) {
space[0].cur = space[i].cur;
}
return i;
}
释放节点
void Free(StaticLinkList space, int j)
{
space[j].cur = space[0].cur;
space[0].cur = j;
}
返回静态链表长度
int StaticLinkListLength(StaticLinkList L)
{
int i = L[MAXSIZE -1].cur;
int length = 0;
while (i) {
++length;
i = L[i].cur;
}
return length;
}
Status StaticListInsert(StaticLinkList L, int i, ElemType e)
{
int cur = Malloc(L);
int k = MAXSIZE -1;
if (L == NULL || i < 1 || i > StaticLinkListLength(L)+1) {
return ERROR;
}
if (cur) {
L[cur].data = e;
for (int m = 0; m < i - 1; m++) {
k = L[k].cur;
}
L[cur].cur = L[k].cur;
L[k].cur = cur;
return OK;
}
return ERROR;
}
Status StaticLinkListDelete(StaticLinkList L, int i)
{
if (L == NULL || i < 1 || i > StaticLinkListLength(L)) {
return ERROR;
}
int k = MAXSIZE - 1;
for (int m = 0; m < i - 1; m++) {
k = L[k].cur;
}
int j = L[k].cur;
L[k].cur = L[j].cur;
Free(L, j);
return OK;
}