我的数据结构与算法也是上到了算法那部分了,先说说我的感想,为什么选择C语言版呢?
我觉得最简单的优势有两个:
1.适合在校生打比赛,因为C语言的执行效率比Java快,对于一些有时间严格限制的比赛很有优势
2.C语言相对Java更容易理解底层原理,如内存操作、指针等概念,而且代码简洁
因为有些小伙伴学校可能大一C语言教学进度没有上指针和结构体那部分,我们学校就是,开头就比较困难了,所有我先补一下数据结构前的一些基础,数组应该都学过,我就不说了
目录
1.指针
什么是指针?先打个预防针,大家一想到指针,就把它认为地址就行,指针是一个变量,存储其他变量的地址。定义指针就指定它的类型就行,比如int、double,但是我们期间用的基本就是int和char,定义在指针前加*号,如:int * p,char * p等
给大家画个图理解一下,假设变量a值为10,在内存中的地址为1001,定义一个int 类型的指针p
把a的地址给p那么通过p就可以找到地址对应的值,因为指针p也是变量,所有它也是有地址的
#include <stdio.h>
int main() {
int a = 10; // 定义一个int变量a,并赋值为10
int *p; // 定义一个指向int类型的指针变量p
p = &a; // 将变量a的地址赋给指针变量p
// 打印变量a的值,使用解引用操作符*来通过指针p访问a的值
printf("%d\n", *p);
return 0;
}
输出结果是10。
2.结构体
结构体也是一种数据类型,只不过是咱们自己定义的,可以存放不同的数据类型。比如:
struct Person {
char name[50];
int age;
float height;
};
定义结构体的关键字:struct 结构的意思 后面接结构体名,跟Java中的类差不多,访问的时候用一个.
strcpy(Person.name, "John Doe");
Person.age = 30;
通过指针来访问就是->
struct Person *ptr = &Person;
ptr->age = 31; // 修改person1的年龄为31
typedef可以简化结构体创建别名,类型定义的意思
typedef struct {
char name[50];
int age;
float height;
} Person;//别名
进入正题吧
数据结构基础
一、线性表
线性表就是一种线性结构,有点数学功底应该都没什么问题,存储零个或者多个数据元素的有限序列,定义特点这些我很少管,知道怎么用就行,写代码多了就理解了。
线性表有两种存储结构
1.顺序存储 (顺序表)
直接把它当成数组就行,下标从0开始,上代码吧
初始化
#define MAX_SIZE 100 // 假设最大容量为100
typedef struct {
int data[MAX_SIZE]; // 存储元素的数组
int length; // 当前长度
} SqList;
define是定义常量的,给它一个最大容量,数组都是有固定容量的
插入元素
在顺序表的第i
个位置之前插入一个新的元素e
int InsertSqList(SqList *L, int i, int e) {
if (i < 1 || i > L->length + 1 || L->length >= MAX_SIZE) return 0; // 参数不合法或表满
for (int j = L->length; j >= i; j--) {
L->data[j] = L->data[j - 1]; // 向后移动元素
}
L->data[i - 1] = e; // 在位置i-1放入新元素
L->length++; // 表长增1,这一步千万别忘
return 1;
}
删除元素
删除第i个位置上的元素,并用e接收
int DeleteSqList(SqList *L, int i, int *e) {
if (i < 1 || i > L->length) return 0; // 参数不合法
*e = L->data[i - 1];
for (int j = i; j < L->length; j++) {
L->data[j - 1] = L->data[j]; // 向前移动元素
}
L->length--; // 表长减1
return 1;
}
遍历顺序表
打印每个元素
void TraverseList(SqList L) {
for (int i = 0; i < L.length; i++) {
printf("%d ", L->data[i]);
}
printf("\n");
}
查找元素
查找顺序表中第一个与给定值e
相等的元素的位置。
int LocateElem(SqList L, int e) {
for (int i = 0; i < L.length; i++) {
if (L->data[i] == e) return i + 1; // 找到匹配项,返回位置(非索引)
}
return 0; // 未找到
}
顺序表优点:随机访问元素效率高,直接通过下标即可
顺序表缺点:就是插入和删除,需要移动大量的数据,效率很低的
所有我们引入第二种结构
二、链式存储(链表)
链表又分为单链表、双链表、循环链表
链表中存在节点,节点中又有数据域和指针域
1.单链表
单链表 中的节点如上,头节点的数据域不存储数据,指针域存储第一个节点的地址,第一个节点的数据域为20,同样的指针域存放第二个节点的地址,依此类推,把它想象成一根链,环环相扣,通过每一个环可以找到它下一个环,但是不能跳到其它环,必须一个一个接的地址找下去
基本操作
1.初始化创建头节点
#include<stdio.h>
typedef int ElemType;
struct Node {
int val;//数据域
struct Node* next;//指向下一个节点的指针域
};
struct Node* createNode() {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));//动态分配内存空间
if (!newNode) return NULL; // 检查内存分配是否成功
newNode->next = NULL;//不确定下一个节点时,让其指向NULL,就是空
return newNode;
}
2.插入节点
头插法
//头插创建单链表
void creLink(LinkList * L,ElemType val) {
LinkList* newNode = new LinkList();
newNode->val = val;
newNode->next =L->next;
L->next = newNode;
}
尾插法
//尾插法
void Tailinsert(LinkList *L,ElemType value) {
LinkList* node = L;
LinkList* newNode =(LinkList * )malloc (sizeof(LinkList));
newNode->val = value;
newNode->next = NULL;
while (node->next!=NULL) {
node = node->next;
}
node->next = newNode;
}
指定位置插入
//指定位置插入节点
void posInsert(LinkList * L,int pos,ElemType val) {
if (pos<0) {
printf("输入的位置不合法");
return;
}
// 查找插入位置
LinkList* current = L;
int i = 0;//位置从0开始
while (current != NULL && i < pos) {
current = current->next;
i++;
}
//找到指定位置
LinkList* newNode = (LinkList *)malloc(sizeof(LinkList));
if (newNode==NULL) {
printf("内存分配失败!");
return;
}
newNode->val = val;
newNode->next = current->next;
current->next = newNode;
}
3.删除节点
//删除节点
void Delete(LinkList* L, ElemType val) {
LinkList* prev = L; // 前驱节点
LinkList* current = L->next; // 当前节点
while (current != NULL) {
if (current->val == val) {//当前节点值与要删除的节点值一致
prev->next = current->next; // 断开连接
free(current); // 释放内存
return; // 删除第一个匹配项后返回
}
prev = current;
current = current->next;
}
printf("未找到值为 %d 的节点\n", val);
}
4.遍历链表
void Print(LinkList * L) {
LinkList* node =L->next;
while (node!=NULL) {
printf("%d ",node->val);
node =node->next;
}
printf("\n");
}
5.查找、更新、获取长度
//查找元素
int Check(LinkList *L,ElemType val) {
LinkList* node = L->next;
int i = 0;
while (node!=NULL) {
if (node->val==val) {
return i;
}
else {
node = node->next;
i++;
}
}
i = -1;
return i;
}
//获取链表长度
int Getlong(LinkList * L) {
int count = 0;
while (L->next!=NULL) {
count++;
L = L->next;
}
return count;
}
//更新节点值
void update(LinkList * L,ElemType val,ElemType value) {
LinkList* node = L->next;
while (node != NULL) {
if (node->val == val) {
node->val = value;
return;
}
else {
node = node->next;
}
}
}
代码测试:
int main() {
LinkList *L;
init(&L);
creLink(&L,1);
creLink(&L,2);
creLink(&L,3);
creLink(&L,4);
Print(L);
Tailinsert(&L,7);
Tailinsert(&L,8);
Tailinsert(&L,9);
Tailinsert(&L,10);
Print(L);
Delete(L,9);
Print(L);//4 3 2 1 7 8 10
printf("\n");
int LinkLength=Getlong(L);
printf("链表长度为:%d",LinkLength);
posInsert(L,2,5);
printf("\n");
Print(L);//4 3 5 2 1 7 8 10
int val;
printf("请输入要查找的元素:\n");
scanf_s("%d",&val);
int pos=Check(L,val);
if (pos==-1) {
printf("元素%d不在该链表中\n",val);
}
else {
printf("节点元素%d在链表的第%d个位置\n", val, pos);
}
update(L,3,9);
Print(L);//4 9 5 2 1 7 8 10
reverse(&L);
Print(L);
}
运行结果:
相比于顺序表,链表在删除和插入的性能上大大得到了优化
2.双链表
有前驱和后继,前驱prior指向前一个节点的地址,后继指向下一个节点地址,根单链表一样,只不过它可以两边找到前后的地址,单链表是单向,双链表就是双向的
代码我就不一样列了吧,直接一步到位。 虽然有点长,但我打的时候没这种感觉,大家习惯就好
#include<stdio.h>
#include <malloc.h>
typedef int ElemType;
struct qlink {
//数据域
ElemType val;
//前驱节点
struct qlink *prior;
//后继节点
struct qlink* next;
};
void init(qlink **L) {//我这个地方用到了双指针,就是传入地址的地址
*L = (qlink*)malloc(sizeof(qlink));
if (!*L) {
printf("内存分配失败!");
return;
}
(*L)->prior = NULL;
(*L)->next = NULL;
}
//头插法
void HeadInsert(qlink ** L,ElemType val) {
qlink * newNode= (qlink*)malloc(sizeof(qlink));
if (!newNode) {
return;
}
newNode->val = val;
newNode->next=(*L)->next;
newNode->prior = *L;
if ((*L)->next!=NULL) {
(*L)->next->prior = newNode;
}
(*L)->next = newNode;
}
//尾插法
void TailInsert(qlink **L,ElemType val) {
//找到尾
qlink* node = (*L);
while (node->next!=NULL) {
node = node->next;
}
//此时到尾部
qlink* newNode = (qlink *)malloc(sizeof(qlink));
if (!newNode) {
return;
}
newNode->val = val;
newNode->next = NULL;
if ((*L)->next == NULL) {
(*L) = newNode;
newNode->prior = (*L);
}
newNode->prior = node;
node->next = newNode;
}
//在指定位置插入
void PosInsert(qlink **L,int pos,ElemType val) {
//找到该位置
qlink* node = (*L);
int i = 0;//假设第一个位置是0
while (i<pos) {
node = node->next;
i++;
}
qlink* newNode = (qlink *)malloc(sizeof(qlink));
newNode->val = val;
newNode->next = node->next;
newNode->prior = node;
node->next->prior = newNode;
node->next = newNode;
}
//删除头部节点
void DeleteHead(qlink **L){
qlink* node = (*L)->next;
(*L)->next = node->next;
node->next->prior = (*L);
free(node);
}
void Print(qlink **L) {
qlink* node = (*L)->next;
while (node!=NULL) {
printf("%d ",node->val);
node = node->next;
}
printf("\n");
}
//删除尾部节点
void DeleteTail(qlink **L) {
qlink* node = (*L)->next;
while (node->next!=NULL) {
node = node->next;
}
node->prior->next = NULL;
}
//删除指定位置节点
void DeletePos(qlink **L,int pos) {
int i = 0;
qlink* node = (*L);
while (i<pos) {
node = node->next;
i++;
}
node->prior->next = node->next;
}
int Check(qlink *L,ElemType val) {
qlink* node = L->next;
int i = 0;
while (node!=NULL) {
if (node->val==val) {
return i+1;
}
else {
node = node->next;
i++;
}
}
i = -1;
return i;
}
//长度
int Getlong(qlink **L) {
qlink* node = (*L);
int length = 0;
while (node->next != NULL) {
node = node->next;
length++;
}
return length;
}
//双向遍历
void BothwayPrint(qlink *L,int val) {
qlink* node = L;
while (node->next!=NULL) {
if (node->val == val) {
break;
}
node = node->next;
}
qlink* newprior = node;
printf("该节点(含该节点)前遍历为:\n");
while (newprior->prior!=NULL) {
printf("%d ", newprior->val);
newprior = newprior->prior;
}
printf("\n");
qlink* newnext = node;
printf("该节点(含该节点)后遍历为:\n");
while (newnext!=NULL) {
printf("%d ", newnext->val);
newnext = newnext->next;
}
}
//释放链表
void Free(qlink **L) {
qlink* cur = (*L);
while (cur!=NULL) {
qlink* newNode = cur->next;
free(cur);
cur = newNode;
}
*L = NULL;
}
int main() {
qlink *L;
init(&L);
HeadInsert(&L,1);
HeadInsert(&L,2);
HeadInsert(&L,3);
HeadInsert(&L,4);
HeadInsert(&L,5);
Print(&L);//5 4 3 2 1
printf("\n");
TailInsert(&L,6);
TailInsert(&L,7);
TailInsert(&L,8);
TailInsert(&L,9);
TailInsert(&L,10);
Print(&L);//5 4 3 2 1 6 7 8 9 10
printf("\n");
PosInsert(&L,2,20);
Print(&L);
int length=Getlong(&L);
printf("链表长度为:%d\n",length);
printf("\n");
DeleteHead(&L);
printf("删除头节点后的链表:\n");
Print(&L);
DeleteTail(&L);
printf("删除尾节点后的链表:\n");
Print(&L);
DeletePos(&L,2);
printf("删除指定位置节点后的链表:\n");
Print(&L);
int val;
printf("请输入要查找的节点的数据域:\n");
scanf_s("%d",&val);
int i=Check(L,val);
if (i==-1) {
printf("找不到该节点");
}
else {
printf("数据域为%d在链表中的%d号节点",val,i);
}
printf("\n");
BothwayPrint(L,1);//从1两边遍历
printf("\n");
Free(&L);
if (L==NULL) {
printf("释放成功!");
}
return 0;
}
代码测试j结果:
做的有点马虎,大家不懂的地方可以相互讨论一下,谢谢支持,后面的明天出!