数据结构笔记
一、 顺序表(一)
1、静态分配顺序表
#include <stdio.h>
#define MaxSize 10 //定义最大长度
typedef struct {
int data[MaxSize]; //用静态数组存放数据元素
int length; //顺序表的当前长度
}SqList; //顺序表的类型定义(静态分配方式)
void InitList(SqList *L) {
for(int i = 0; i < MaxSize; i++)
L->data[i] = 0; //将所有数据元素设置为默认初始值,不初始化会有脏数据
L->length = 0; //顺序表初始长度为0
}
void GetElem(SqList L, int i) {
for (int j = 0; j < i; ++j) {
printf("data[%d] = %d\n", j, L.data[j]);
}
}
int main() {
SqList L; //声明一个顺序表
InitList(&L); //初始化顺序表
GetElem(L, MaxSize);
return 0;
}
2、动态分配顺序表
#include <stdlib.h>
#define InitSize 10
typedef struct {
int *data; //动态分配数组的指针
int MaxSize; //顺手表的最大容量
int length; //顺序表的当前长度
}SeqList; //顺序表的类型定义(动态分配方式)
//C语言动态申请和释放内存空间
//L.data = (ElemType *)malloc(sizeof(ElemType) * InitSize);
//malloc函数返回一个指针,需要强制转型为指定的数据元素类型指针
void InitList(SeqList *L){
L->data = (int *) malloc (InitSize * sizeof(int));
L->length = 0;
L->MaxSize = InitSize;
}
void IncreaseSize(SeqList *L, int len){
int *p = L->data; //用p指针指向旧顺序表的第一个元素地址,这一步相当于把旧顺序表重新起名为p了,所以最后释放的旧表就是p了
L->data = (int *) malloc((L->MaxSize + len) * sizeof(int)); //旧顺序表的第一个元素指向一个新申请的顺序表的第一个元素地址
for (int i = 0; i < L->length; ++i) {
L->data[i] = p[i]; //使新顺序表的元素等于旧顺序表的元素,下标一一对应,最后新的顺序表会空出五个空间
}
L->MaxSize = L->MaxSize + len; //顺序表最大长度增加len个
free(p); //释放原来的内存空间
}
int main() {
SeqList L;
InitList(&L);
IncreaseSize(&L, 5);
return 0;
}
3、顺序表的特点:
- 随机访问,即可以在O(1)时间内找到第i个元素
- 存储密度高,每个节点只存储数据元素
- 拓展容量不方便(即便采用动态分配的方式实现,拓展长度的时间复杂度也比较高)
- 插入、删除操作不方便,需要移动大量元素
4、顺序表的插入
#define MaxSize 10
typedef struct {
int data[MaxSize];
int length;
}SqList;
void InitList(SqList *L) {
for(int i = 0; i < MaxSize; i++)
L->data[i] = 0;
L->length = 0;
}
bool ListInsert(SqList *L, int i, int e){
if(i < 1 || i > L->length+1) //判断i的范围是否有效
return false;
if(L->length >= MaxSize) //当前存储空间已满,不能插入
return false;
for (int j = L->length; j >= i ; --j) { //将第i个元素及之后的元素后移
L->data[j] = L->data[j-1];
}
L->data[i-1] = e; //在位置i处放入e,传入的i是位序,数组的下标从0开始,所以位序i对应的下标是i-1
L->length++; //长度加1
return true;
}
int main(){
SqList L;
InitList(&L);
ListInsert(&L, 1, 1); //在第一个位置插入元素1
ListInsert(&L, 2, 2);
for(int i = 0; i < L.length; i++) {
printf("data[%d] = %d\n", i, L->data[i]);
}
}
5、顺序表的删除
#define MaxSize 10
typedef struct {
int data[MaxSize];
int length;
}SqList;
void InitList(SqList *L) {
for(int i = 0; i < MaxSize; i++)
L->data[i] = i;
L->length = 10;
}
bool ListDelete(SqList *L, int i, int *e){
if(i<1 || i>L->length) //判断i的范围是否有效
return false;
*e = L->data[i-1]; //将被删除的元素赋值给e
for (int j = i; j < L->length; ++j) { //将第i个位置后的元素前移
L->data[j-1] = L->data[j];
}
L->length--; //线性表长度减1
return true;
}
int main(){
SqList L;
InitList(&L);
int e = -1;
if(ListDelete(&L, 3, &e))
printf("已删除第三个元素,删除元素为%d\n", e);
else
printf("位序i不合法,删除失败\n");
}
6、顺序表的查找
按位查找:
#define MaxSize 10
typedef struct {
int data[MaxSize];
int length;
}SqList;
void InitList(SqList *L) {
for(int i = 0; i < MaxSize; i++)
L->data[i] = i;
L->length = 10;
}
int GetElem(SqList L, int i){
return L.data[i-1];
}
int main(){
int data = 0;
SqList L;
InitList(&L);
data = GetElem(L, 2);
printf("data = %d\n", data);
}
按值查找:
#define InitSize 10
typedef struct {
int *data; //动态分配数组的指针
int MaxSize; //顺手表的最大容量
int length; //顺序表的当前长度
}SeqList; //顺序表的类型定义(动态分配方式)
void InitList(SeqList *L) {
for(int i = 0; i < InitSize; i++)
L->data[i] = i;
L->length = 10;
}
int LocateElem(SeqList L, int e){
for (int i = 0; i < L.length; ++i) {
if(L.data[i] == e)
return i+1; //数组下标为i的元素值等于e,返回其位序i+1
}
return 0; //退出循环,说明查找失败
}
int main(){
int result = 0;
SeqList L;
InitList(&L);
result = LocateElem(L, 3);
printf("result = %d\n", result);
}
二、单链表
1、不带头结点的单链表
typedef struct LNode{ //定义单链表结点类型
int data; //每个结点存放一个数据元素
struct LNode *next; //每个结点都有一个指向下一个结点的指针
}LNode, *LinkList; //使用typedef重命名struct LNode为LNode,重命名struct LNode *LinkList为*LinkList, *LinkList是用来指向单链表第一个结点的指针
//LinkList等价于LNode *,前者强调这是一个链表,后者强调这是一个结点
//初始化一个没有头结点的空链表
bool InitList(LinkList *L){
L = NULL; //空表,暂时还没有任何结点
return true;
}
//判断一个单链表是否为空
//bool Empty(LinkList L){
// if(L == NULL)
// return true;
// else
// return false;
//}
bool Empty(LinkList L){
return (L == NULL);
}
void main(){
LinkList L; //声明一个指向单链表的指针,即头指针
InitList(L);
}
2、带头结点的单链表
typedef struct LNode{ //定义单链表结点类型
int data; //每个结点存放一个数据元素
struct LNode *next; //每个结点都有一个指向下一个结点的指针
}LNode, *LinkList;
bool InitList(LinkList L){ //LinkList类型是指针类型
L = (LNode *) malloc(sizeof(LNode)); //分配一个头结点,返回头结点的地址给指针L,可以把L就当做头指针理解
if(L == NULL) //内存不足,分配失败
return false;
L->next = NULL; //分配成功,头结点的next指针之后没有其他结点
return true;
}
//判断一个带头结点的单链表是否为空
//bool Empty(LinkList L){
// if(L->next == NULL)
// return true;
// else
// return false;
//}
int main(){
LinkList L; //声明一个指向单链表的指针,即头指针
InitList(L); //初始化单链表,即增加一个头结点,初始化完成之后,头指针指向头结点
}
3、单链表插入
按位序插入
typedef struct LNode{ //定义单链表结点类型
int data; //每个结点存放一个数据元素
struct LNode *next; //每个结点都有一个指向下一个结点的指针
}LNode, *LinkList;
bool InsertNode(LNode *p, int e){
if(p==NULL)
return false;
LNode *s = (LNode *) malloc(sizeof(LNode));
if(s==NULL)
return false;
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
LinkList InitList(){ //LinkList类型是指针类型
LinkList L; //声明一个单链表头指针
L = (LNode *) malloc(sizeof(LNode)); //分配一个头结点,返回头结点的地址给指针L,可以把L就当做头指针理解
if(L == NULL) //内存不足,分配失败
return false;
L->next = NULL; //分配成功,头结点的next指针之后没有其他结点
return L; //返回单链表的头指针,此时单链表里面已经有一个头指针和一个头结点
}
bool ListInsert(LinkList L, int i, int e){
if(i < 1)
return false;
// if(i==1){ //不带头结点的单链表插入第一个元素时需要单独处理
// LNode *s = (LNode *)malloc(sizeof(LNode));
// s->data = e;
// s->next = L;
// L = s;
// return true;
// }
LNode *p; //指针p指向当前扫描到的结点
int j = 0; //当前p指向的是第几个结点
p = L; //一开始p会指向头结点,头结点是第0个结点
while (p != NULL && j < i-1){ //循环找到第i-1个结点
p = p->next;
j++;
}
// if(p == NULL) //i值不合法
// return false;
// LNode *s = (LNode *) malloc(sizeof(LNode));
// s->data = e;
// s->next = p->next; //先把新结点的next指针指向下一个结点
// p->next = s; //再把前一个结点的next指针指向新结点
// return true;
return InsertNode(p, e); //使用后插入函数代替以上代码
}
void InsertNextNode(LinkList L, int x, int y){ //在单链表值为x的结点后面插入一个值为3的结点
LinkList pre, p, s;
pre = L;
p = L->next;
while (p && p->data!=x){ //找到值为x的结点,获取它得next指针
pre = p;
p = p->next;
}
if(p){
s = (LNode *) malloc (sizeof(LNode));
s->data = y;
s->next = p;
pre->next = s;
}
}
bool InsertPriorNode(LNode *p, int e){ //在给定结点前加一个结点,其实就是把新结点放到后面,再交换两个结点的值
if(p==NULL)
return false;
LNode *s = (LNode *) malloc(sizeof(LNode));
if(s==NULL)
return false;
s->next=p->next;
p->next=s; //新结点s放到p后面
s->data=p->data;
p->data=e; //p和s值互换
return true;
}
int main(){
LNode *p;
LinkList L; //新建一个单链表,头指针为L
L = InitList(); //初始化新建的单链表,此时单链表里面就有一个头指针指向头结点
ListInsert(L, 1, 0); //在单链表L的第一个位置插入元素0,此时单链表里面就有一个头指针指向头结点,头结点的next指针指向插入的元素0这个结点
ListInsert(L, 2, 1);
ListInsert(L, 3, 2);
InsertNextNode(L, 1, 3); //在单链表值为1的结点后面插入一个值为3的结点
for(p = L->next; p!=NULL; p=p->next){
printf("%d ", p->data);
}
}
4、单链表删除
typedef struct LNode{ //定义单链表结点类型
int data; //每个结点存放一个数据元素
struct LNode *next; //每个结点都有一个指向下一个结点的指针
}LNode, *LinkList;
LinkList InitList(){ //LinkList类型是指针类型
LinkList L; //声明一个单链表头指针
L = (LNode *) malloc(sizeof(LNode)); //分配一个头结点,返回头结点的地址给指针L,可以把L就当做头指针理解
if(L == NULL) //内存不足,分配失败
return false;
L->next = NULL; //分配成功,头结点的next指针之后没有其他结点
return L;
}
bool ListInsert(LinkList L, int i, int e){
if(i < 1)
return false;
LNode *p; //指针p指向当前扫描到的结点
int j = 0; //当前p指向的是第几个结点
p = L; //p指向头结点,头结点是第0个结点
while (p != NULL && j < i-1){ //循环找到第i-1个结点
p = p->next;
j++;
}
// if(p==NULL)
// return false;
// if(p->next == NULL) //第i-1个结点之后已无其他结点
// return false;
// LNode *q = p->next; //令q指向被删除的结点
// *e = q->data; //用e返回元素的值
// p->next = q->next; //将*q结点从单链表中断开
// free(q); //释放结点的存储空间
// return true;
return InsertNextNode(p, e); //以上代码可以封装为InsertNextNode函数
}
bool ListDelete(LinkList L, int i, int *e){
if(i < 1)
return false;
// LNode *p; //指针p指向当前扫描到的结点
// int j = 0; //当前p指向的是第几个结点
// p = L; //p指向头结点,头结点是第0个结点
// while (p != NULL && j < i-1){ //循环找到第i-1个结点
// p = p->next;
// j++;
// }
LNode *p = GetElem(L, i-1); //将以上代码封装为GetElem查找函数
if(p==NULL)
return false;
if(p->next == NULL) //第i-1个结点之后已无其他结点
return false;
LNode *q = p->next; //令q指向被删除的结点
*e = q->data; //用e返回元素的值
p->next = q->next; //将*q结点从单链表中断开
free(q); //释放结点的存储空间
return true;
}
bool DeleteNode(LNode *p){ //删除给定结点的后一个结点
if(p==NULL)
return false;
LNode *q = p->next; //令q指向*p的后继结点
p->data = p->next->data; //和后继结点交换数据域,但如果传入的结点是单链表的最后一个结点,这行就会报错,因为p->next为NULL
p->next = q->next; //将*q结点从单链表中断开
free(q); //释放后继结点的存储空间
return true;
}
int main(){
int *e;
LNode *p;
LinkList L;
L = InitList();
ListInsert(L, 1, 0);
ListInsert(L, 2, 1);
ListInsert(L, 3, 2);
ListInsert(L, 4, 3);
ListDelete(L, 2, &e);
for(p = L->next; p!=NULL; p=p->next){
printf("%d ", p->data);
}
printf("\n");
printf("%d\n", e);
}
5、单链表的查找(带头结点)
按位查找
LNode * GetElem(LinkList L, int i){
if(i<0)
return NULL;
LNode *p; //指针p指向当前扫描到的结点
int j = 0; //当前p指向的是第几个结点
p = L; //L指向头结点,头结点是第0个结点
while (p!=NULL && j<i){ //循环找到第i个结点
p=p->next;
j++;
}
return p;
}
按值查找
LNode * LocateElem(LinkList L, int e){
LNode *p = L->next;
while (p != NULL && p->data != e) //从第1个结点开始查找数据域为e的结点
p = p->next;
return p; //找到后返回该结点指针,否则返回NULL
}
求单链表的长度
int ListLength(LinkList L){
int len = 0;
LNode *p = L;
while (p->next != NULL){
p = p->next;
len++;
}
return len;
}