下面的叙述如果有什么错误,还请批评指正,谢谢。
一、数据结构与算法简介
1.什么是数据结构?
简单的说法,就是数据以及数据之间存在的关系
数据之间的关系分为:逻辑关系和物理关系(逻辑结构和物理结构)
逻辑关系:即人为赋予的关系:
一对一:线性表 栈 队列
一对多:树
多对多:图
物理关系:数据在计算机中的实际存储。
这就告诉我们,在选择数据的存储结构的时候,需要我们理清楚数据之间结构,从而选择一个合适的物理结构。
2.数据结构与算法
有这么一个式子,相信大家不会陌生:
程序=结构+算法
数据结构上面已经介绍过,那么什么是算法呢?
所谓的算法就是要完成一个任务具体的实现步骤,那么这个步骤肯定是有优劣之分的,一个好的算法,步骤少,效率高,这也是作为程序员一直追求的东西。这里就引出了两个概念:时间复杂度和空间复杂度,一个算法的好坏就是由这两个复杂度来衡量的。
时间复杂度:大O估算法 O(频度)
i+=1; O(1)
for(i=0;i<n;i++) {} O(n)
for(i=0;i<n;i++){for(j=0;j<n;j++){}} O(n^2)
这个是不是一看就明白了如何计算时间复杂度?
空间复杂度
是对一个算法在运行过程中临时占用存储空间大小的量度。一般情况下,时间复杂度与空间复杂度不可兼得:减少时间复杂度,就势必会导致空间复杂度的增大,反过来,想要减少空间复杂度,就肯定会导致时间复杂度的增大。这就需要我们根据实际需要来做合理取舍了。
好,有关数据结构和算法的概念,就说这么多 ,下面我们的重点,放在数据的存储结构上,注意,需要学习的是他的思想,而非代码,所以重在理解!!!
二、四大逻辑结构
1.集合结构:所有元素在同一个集合上,他们之间没啥关系,这里不做过多的介绍 ;
2.线性结构:一对一的关系:如链表、栈、队列,这里使我们掌握的重点;
3.树形结构:一对多的关系,我们主要学习二叉树;
4.图形结构:多对多的关系,这个在这里也不做过多的叙述。
三、链表
链表属于线性表,线性表的存储分为顺序存储和链式存储,我们接下来要讲的链表,属于链式存储,具体的关系,如下图所示:
这里需要特别注意的是,我们学习单链表和双向不循环链表都是为了学习双向循环链表,在今后的工作学习中,大多数用的都是双向循环链表。下面的介绍,我们就按照单向不循环->单向循环->双向不循环->双向循环,这样循序渐进的方式来讲述。注意,对于数据结构,我们更重要的是理解,而不只是盯着代码不放,切记!
1.单向不循环链表
他的结构示意图如下:
(忽略其中的错别字,嘿嘿)
链表,分为指针域和数据域,二者构成一个节点(对应上面一个方框),为了方便理解,如上图,我们把方框的上面部分叫做“数据域”,下面部分看成“指针域”(当然,这个上面下面的顺序无所谓),对于单向链表来说,每个节点的指针(next)都指向下一个节点的地址,最后一个节点的next,指向的是NULL(不循环链表)或者头节点(循环链表)。其中,头结点只是为了方便数据的索引而存在,他的数据域不存放有效数据,有时候为了方便,我们会在头结点的数据域里面存放链表节点的个数(链表的大小)。从首元节点开始,存放有效数据的第一个元素。在C语言中,我们可以这样来表示一个节点:
struct Node
{
int m_Data;
struct Node *next;
};
typedef struct Node node_t;
当然,数据域不仅仅可以是单一的数据类型,也可以是多个数据类型,还可以是一个结构体,所以,还可以这样:
struct Data
{
char m_Name[20];
int m_Age;
double m_score;
};
typedef struct Data data_t;
struct Node
{
data_t m_Data;
struct Node *next;
};
typedef struct Node node_t;
这个不管怎么样,只要符合上面的规则,都是可以的,这里只是举了两个例子。
学了数据结构的朋友都知道,对链表的操作,无非就是增删改查。
下面我们来看看操作步骤:
- 1.确定单向链表节点;
- 2.初始化链表;
- 3.插入某个数据到某个位置;
- 4.打印链表;
- 5.删除某个位置的数据;
- 6.查询某个数据在节点中的位置
- 7.更改某个数据为新的数据
对照上面的视图,理解一下下面的C语言代码:
#include <stdio.h>
#include <stdlib.h>
#define SYSERR(x,option,y,a,k) if((x)option(y))\
{\
printf("%s:%s:%d:%s\n",__FILE__,__func__,__LINE__,a);\
return (k);\
}
struct Node
{
int data;
int list_length;
struct Node *next;
};
typedef struct Node node_t;
/*
*链表初始化
*参数:void
*返回值:初始化后的链表头结点
*/
node_t* listInit(void)
{
node_t* head = (node_t *)malloc(sizeof(node_t));
SYSERR(head,==,NULL,"[malloc err]",(node_t *)-1); /* 判断是否空间申请失败 */
head->next = NULL;
head->list_length = 0;
return head;
}
/*
*头插
*参数:head:要插入的链表 dat:要插入的数据
*返回值:返回头插后的链表头结点
*/
node_t* insertHead(node_t* head,int dat)
{
SYSERR(head,==,NULL,"[list is empty]",NULL);
node_t* temp = (node_t *)malloc(sizeof(node_t));
SYSERR(temp,==,NULL,"[malloc err]",NULL);
temp->data = dat;
temp->next = head->next;
head->next = temp;
head->list_length++;
return head;
}
/*
*尾插
*参数:head:要插入的链表 dat:要插入的数据
*返回值:返回尾插后的链表头结点
*/
node_t* insertTail(node_t* head,int dat)
{
SYSERR(head,==,NULL,"[list is empty]",NULL);
node_t* temp = (node_t *)malloc(sizeof(node_t));
SYSERR(temp,==,NULL,"[malloc err]",NULL);
node_t* temp1 = (node_t *)malloc(sizeof(node_t));
SYSERR(temp1,==,NULL,"[malloc err]",NULL);
temp = head;
for(;temp->next != NULL; temp = temp->next);
temp1->data = dat;
temp1->next = NULL;
temp->next = temp1;
head->list_length++;
return head;
}
/*
*打印链表
*参数:head:要打印的链表
*返回值:void
*/
int displayList(node_t* head)
{
SYSERR(head,==,NULL,"[list is empty]",-1);
node_t* temp = head->next;
for(;temp != NULL; temp = temp->next)
{
printf("-> %d ",temp->data);
}
putchar(10);
return 0;
}
/*
*删除节点
*参数:head:要删除节点的链表 dat:要删除的数据
*返回值:删除节点后的链表首节点
*/
node_t* deleteNode(node_t* head,int dat)
{
int num = 0,i = 0,flag = 0;
SYSERR(head,==,NULL,"[list is empty]",NULL);
node_t* temp = head->next;
for(; temp != NULL; temp = temp->next)
{
num++;
if(temp->data == dat)
{
flag++;
break;
}
if(flag != 1 && num == head->list_length) {printf("list has not %d\n",dat);return head;}
}
node_t* node = head->next;
node_t* q = node;
if(num == 1) /* 第一个 */
{
head->next = node->next;
head->list_length--;
free(q);
}
else
{
for(i = 0; i < num-2; i++)
{
node = node->next;
}
node_t* p = node->next;
node->next = node->next->next;
head->list_length--;
free(p);
}
return head;
}
int main(void)
{
node_t* head = listInit();
#if 1
head = insertHead(head,2);
head = insertHead(head,5);
head = insertHead(head,6);
head = insertHead(head,7);
head = insertHead(head,53);
head = insertHead(head,34);
printf("头插后:");
(void)displayList(head);
printf("链表长度为:%d\n\n",head->list_length);
#endif
#if 1
head = insertTail(head,78);
head = insertTail(head,23);
head = insertTail(head,100);
head = insertTail(head,75);
head = insertTail(head,37);
head = insertTail(head,29);
printf("尾插后:");
(void)displayList(head);
printf("链表长度为:%d\n\n",head->list_length);
#endif
#if 1
head = deleteNode(head,7);
printf("删除一个节点后:");
(void)displayList(head);
printf("链表长度为:%d\n\n",head->list_length);
#endif
return 0;
}
代码运行结果如下:
上面例程中,只有初始化、头插、尾插、删除某个节点,只要弄懂这些,剩下的修改查询都是很简单的,这里就不写了。接下来的例程中同样只有这几个部分。这里就一并说明了。
2.单向循环链表
和上面的不循环链表类似,他的模型如下:
区别就在于最后一个节点的next指向的是头结点而已。那么对于他的操作也是一样的:
- 1.确定单向链表节点;
- 2.初始化链表;
- 3.插入某个数据到某个位置;
- 4.打印链表;
- 5.删除某个位置的数据;
- 6.查询某个数据在节点中的位置
- 7.更改某个数据为新的数据
注意下面程序上的细微差别:
#include <stdio.h>
#include <stdlib.h>
#define SYSERR(x,option,y,b,k) if((x)option(y))\
{\
printf("%s:%s:%d:%s\n",__FILE__,__func__,__LINE__,b);\
return (k);\
}
struct Node
{
int data;
int list_length;
struct Node *next;
};
typedef struct Node node_t;
/*
*链表初始化
*参数:void
*返回值:初始化后的链表头结点
*/
node_t* listInit(void)
{
node_t* head = (node_t *)malloc(sizeof(node_t));
SYSERR(head,==,NULL,"[malloc err]",(node_t *)-1); /* 判断是否空间申请失败 */
head->next = head; /* 自己指向自己 */
head->list_length = 0;
return head;
}
/*
*头插
*参数:head:要插入的链表 dat:要插入的数据
*返回值:返回头插后的链表头结点
*/
node_t* insertHead(node_t* head,int dat)
{
SYSERR(head,==,NULL,"[list is empty]",NULL);
node_t* temp = (node_t *)malloc(sizeof(node_t));
SYSERR(temp,==,NULL,"[malloc err]",NULL);
temp->data = dat;
temp->next = head->next;
head->next = temp;
head->list_length++;
return head;
}
/*
*尾插
*参数:head:要插入的链表 dat:要插入的数据
*返回值:返回尾插后的链表头结点
*/
node_t* insertTail(node_t* head,int dat)
{
SYSERR(head,==,NULL,"[list is empty]",NULL);
node_t* temp = (node_t *)malloc(sizeof(node_t));
SYSERR(temp,==,NULL,"[malloc err]",NULL);
node_t* temp1 = (node_t *)malloc(sizeof(node_t));
SYSERR(temp1,==,NULL,"[malloc err]",NULL);
temp = head->next;
for(;temp->next != head; temp = temp->next);
temp1->data = dat;
temp1->next = head;
temp->next = temp1;
head->list_length++;
return head;
}
/*
*打印链表
*参数:head:要打印的链表
*返回值:void
*/
int displayList(node_t* head)
{
SYSERR(head,==,NULL,"[list is empty]",-1);
node_t* temp = head->next;
for(;temp != head; temp = temp->next)
{
printf("-> %d ",temp->data);
}
putchar(10);
return 0;
}
/*
*删除节点
*参数:head:要删除节点的链表 dat:要删除的数据
*返回值:删除节点后的链表首节点
*/
node_t* deleteNode(node_t* head,int dat)
{
int num = 0,i = 0,flag = 0;
SYSERR(head,==,NULL,"[list is empty]",NULL);
node_t* temp = head->next;
for(; temp != head; temp = temp->next)
{
num++;
if(temp->data == dat)
{
flag = 1;
break;
}
if(flag != 1 && num == head->list_length){printf("list has not %d\n",dat);return head;}
}
node_t* node = head->next;
node_t* q = node;
if(num == 1)
{
head->next = node->next;
head->list_length--;
free(q);
}
else
{
for(i = 0; i < num-2; i++)
{
node = node->next;
}
node_t* p = node->next;
node->next = node->next->next;
head->list_length--;
free(p);
}
return head;
}
int main(void)
{
node_t* head = listInit();
#if 1
head = insertHead(head,2);
head = insertHead(head,5);
head = insertHead(head,6);
head = insertHead(head,7);
head = insertHead(head,53);
head = insertHead(head,34);
printf("头插后:");
(void)displayList(head);
printf("链表长度为:%d\n\n",head->list_length);
#endif
#if 1
head = insertTail(head,78);
head = insertTail(head,23);
head = insertTail(head,100);
head = insertTail(head,75);
head = insertTail(head,37);
head = insertTail(head,29);
printf("尾插后:");
(void)displayList(head);
printf("链表长度为:%d\n\n",head->list_length);
#endif
#if 1
head = deleteNode(head,53);
printf("删除一个节点后:");
(void)displayList(head);
printf("链表长度为:%d\n\n",head->list_length);
#endif
return 0;
}
运行结从上面两个链表来看,无论是循环还是不循环的,都存在着以下的问题:
**只能是一个方向遍历,也就是说,只能找到后继,无法找到前驱。这给数据的操作带来一定的复杂度。**有关前驱和后继,如下图:
双链表请看:
数据结构02----C语言双向链表
栈:
数据结构03----C语言栈
队列:
数据结构04----队列