抓紧时间学习了,一步一个脚印,绝不松懈,这是本系列的第一篇,算法和数据结构是程序员你的必修课,面试看懂别人复杂的程序这些都是基本功,需要加强,所以有了下面的文章,2010.2.4 6.24分
数据结构和算法学习笔记(1)
首先给出一个最基本的结论:我们做算法和结构其实说白了,就是空间换时间,或者时间换空间。要看需求了。一般的情况下,大家都喜欢空间换时间。因为内存大嘛,总是用啊用不完。
最基本的一些结构就略过了,从栈开始吧。
1 堆栈
堆栈即为栈,下面是一个数组实现的栈:该栈固定大小,即存放的元素最大值固定,数组第0位表示栈底。如果需要存入不确定数量的数据,请使用链表来实现栈。
class Stack
{
public:
Stack(int iAmount = 10); //设置栈所在数组的最大值
~Stack();
int Pop(int&iVal); //出栈
int Push(intiVal); //入栈
int Top(int&iVal); //栈顶元素
private:
int *m_pData; //栈所在数据首地址。。简单起见用int应该改用泛型
int m_iCount; //使用的元素个数
int m_iAmount; //栈最大元素个数
};
Stack::Stack(int iAmount)
{
m_pData= new int[iAmount];//连续的数组,其中固定大小来划分栈中元素
m_iCount= 0; //初始化栈内元素为0个
m_iAmount= iAmount;
}
Stack::~Stack()
{
delete m_pData;
}
int Stack::Pop(int&iVal)
{
if(m_iCount>0)
{
--m_iCount; //栈内元素--
iVal= m_pData[m_iCount];
return 1;
}
return 0;
}
int Stack::Push(int iVal)
{
if(m_iCount<m_iAmount)
{
m_pData[m_iCount]= iVal;
++m_iCount;
return 1;
}
return 0;
}
int Stack::Top(int&iVal)
{
if(m_iCount>0 && m_iCount<=m_iAmount)
{
iVal= m_pData[m_iCount-1];
return 1;
}
return 0;
}
2 队列
下面是一个利用队列来对树进行广度优先检索。
广度优先区别于深度优先,即优先遍历最靠近根节点的各个节点:
我们的算法是:
1,根节点入队
2,出队一个节点,算一次遍历,直到队列为空
3,将刚出队的节点的子节点入队
4,转到2
队列的状况如下图:
// The Node
//////////////////////////////////////////////////////////////////////////
struct Node //树的每个节点
{
Node(char cChar, intiSubNodeNum=0);
~Node();
char m_cChar; //该节点编号例如(A.B.C……)
int m_iSubNodeNum; //该节点的子节点数目
Node**m_arrNodePointer; //指向子节点
};
Node::Node(char cChar, intiSubNodeNum)
{
m_cChar= cChar;
m_iSubNodeNum= iSubNodeNum;
if(iSubNodeNum!=0)
m_arrNodePointer= new Node*[iSubNodeNum];
else
m_arrNodePointer= NULL;
}
Node::~Node()
{
if(m_arrNodePointer!=NULL)
delete[] m_arrNodePointer;
}
// The Queue
//////////////////////////////////////////////////////////////////////////
class Queue
{
public:
Queue(int iAmount=10);
~Queue();
//return 0 means failed, return 1 means succeeded.
int Enqueue(Node* node);
int Dequeue(Node* & node);
private:
int m_iAmount;
int m_iCount;
Node**m_ppFixed; //The pointer array to implement thequeue.
int m_iHead;
int m_iTail;
};
Queue::Queue(int iAmount)
{
m_iCount= 0; //队列中已经使用的元素
m_iAmount= iAmount; //队列最大元素个数
m_ppFixed= new Node*[iAmount]; //初始化一个数组保存元素
m_iHead= 0; //头位置,即取元素位置,在数组开头
m_iTail= iAmount-1; //插入元素位置。在数组尾部
}
Queue::~Queue()
{
delete[] m_ppFixed;
}
int Queue::Enqueue(Node* node)
{
if(m_iCount<m_iAmount)
{
++m_iTail;
if(m_iTail > m_iAmount-1)
m_iTail= 0;
m_ppFixed[m_iTail]= node;
++m_iCount;
return 1;
}
else
return 0;
}
intQueue::Dequeue(Node* & node)
{
if(m_iCount>0)
{
node= m_ppFixed[m_iHead];
++m_iHead;
if(m_iHead > m_iAmount-1)
m_iHead= 0;
--m_iCount;
return 1;
}
else
return 0;
}
// Main
//////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[])
{
//Construct the tree.
NodenA('A', 3);
Node nB('B',2);
Node nC('C');
Node nD('D',3);
Node nE('E');
Node nF('F',2);
Node nG('G');
Node nH('H',1);
Node nI('I');
Node nJ('J');
Node nK('K');
Node nL('L');
nA.m_arrNodePointer[0] = &nB; //指向子节点
nA.m_arrNodePointer[1] = &nC;
nA.m_arrNodePointer[2] = &nD;
nB.m_arrNodePointer[0] = &nE;
nB.m_arrNodePointer[1] = &nF;
nD.m_arrNodePointer[0] = &nG;
nD.m_arrNodePointer[1] = &nH;
nD.m_arrNodePointer[2] = &nI;
nF.m_arrNodePointer[0] = &nJ;
nF.m_arrNodePointer[1]= &nK;
nH.m_arrNodePointer[0]= &nL;
Queueque;
que.Enqueue(&nA); //根节点A入队列
Node*pNode;
while (que.Dequeue(pNode)==1) //从队列中取出一个元素(第一次自然是A);第二次取出B
{
printf("%c ", pNode->m_cChar); //第一次输出A;第二次输出B
int i;
for(i=0; i<pNode->m_iSubNodeNum; i++) //第一次为A的三个子节点B,C,D入队列;第二次将B的子节点E,F加入队列。
{
que.Enqueue(pNode->m_arrNodePointer[i]);
}
}
return 0;
}
3 二分查找
//参数:有序数组、要查找的数字、最低位(一般为)、最高位(一般为数组最大下标)
static int
dichotomy_search(short a[], ems_int32 num, ems_int32 low, ems_int32high)
{
ems_int32mid;
if(low > high)
{
return FATAL_ERROR; //没有查到。。
}
mid= (low + high)/2; //中间
if(num == a[mid])
{
return mid; //找到
}
else
{
if(num < a[mid]) //在左边
{
return dichotomy_search(a, num, low, mid-1);
}
else //在右边
{
return dichotomy_search(a, num, mid+1, high);
}
}
}
4 散列
一般就是一个数组,通过散列算法定位到数组某个元素,当然这样可能会重复,你可以继续散列,或者做链表等等方法来处理。
1,除法散列法
最直观的一种,上图使用的就是这种散列法,公式:
index = value % 16
学过汇编的都知道,求模数其实是通过一个除法运算得到的,所以叫“除法散列法”。
2,平方散列法
求index是非常频繁的操作,而乘法的运算要比除法来得省时(对现在的CPU来说,估计我们感觉不出来),所以我们考虑把除法换成乘法和一个位移操作。公式:
index = (value * value) >> 28
如果数值分配比较均匀的话这种方法能得到不错的结果,但我上面画的那个图的各个元素的值算出来的index都是0——非常失败。也许你还有个问题,value如果很大,value * value不会溢出吗?答案是会的,但我们这个乘法不关心溢出,因为我们根本不是为了获取相乘结果,而是为了获取index。
可能有其他类型的输入,随机应变吧。
5 树
深度优先遍历又可分为:前序遍历(Preorder Traversal),后序遍历(Postorder Traversal)和中序遍历(Inorder Traversal),其中中序遍历只有对二叉树才有意义
//////////////////////////////////////////////////////////////////////////
struct TreeNode //树节点
{
char m_cVal; //节点值
TreeNode*m_pLeft; //左右子节点
TreeNode*m_pRight;
TreeNode(char cVal);
};
TreeNode::TreeNode(char cVal)
{
m_cVal= cVal;
m_pLeft= 0;
m_pRight= 0;
}
// A
// / \
// / \
// B C
// \ / \
// D E F
// \ \
// G H
// / \
// I J
// / \
// K L
// TreeNode
中序遍历:从A开始
TreeNode p = 根节点A;
while( p != NULL)
{
if(p有左节点)
{
P入栈 //1
p= p的左子节点//2
}
else if (p有右子节点)
{
输出P //1
p= p的右子节点//2
}
else //P无子节点了
{
输出P
如果栈里有值,弹出一个
如果栈里没有值了 break;
}
}
另外可以用递归实现,很简单。
如果需要查找和插入都很快,那么无疑应该选用二叉查找树。但是这种树的删除效率略低。如果用在只需要查找和插入的地方,比如构建属性表,空间信息数据则非常好。
但是,应该了解,二叉搜索树如果根节点的值选的不好或者插入的顺序不好,会使树非常之深,导致搜索插入效率急剧降低。那么就需要平衡二叉搜索树了。平衡二叉树的查找和删除和二叉搜索树一模一样。关键是构造的时候算法问题,主要分为4种情况,具体的算法可网上参阅。
6 二叉堆
一种特殊的队列,总是最小的元素先出。插入和取出都很快速,复杂度logn
基本概念:
1 二叉堆是一种特殊的完全二叉树,完全二叉树的最大特点在于不需要指针来表明左右节点。可以直接利用数组来保存完全二叉树,利用偏移来找到需要的元素。
2 其中最小的元素总在根节点。
3 入队原则:将新加入的元素放到树(数组)的最后面,然后依次和父节点比较,如果比父节点小,则交换位置。如此循环,知道无法交换为止。
4 出队原则:不好写。略
//交换个整形
//example:SWAP_TWO_INT(7,8)
// a = 0111^1000 =0000
// b = 1000^0000 =1000
// a = 0000^1000 =0111
#define SWAP_TWO_INT(a, b) \
a^=b;b^=a; a^=b;
class CBinaryHeap
{
public:
CBinaryHeap(int iSize = 100);
~CBinaryHeap();
int Enqueue(intiVal);
int Dequeue(int&iVal);
int GetMin(int&iVal);
#ifdef _DEBUG
void PrintQueue();
#endif
protected:
int *m_pData; //保存二叉堆的数组
int m_iSize; //二叉堆最大容量
int m_iAmount; //目前数目
};
CBinaryHeap::CBinaryHeap(int iSize)
{
m_pData= new int[iSize];
m_iSize= iSize;
m_iAmount= 0;
}
CBinaryHeap::~CBinaryHeap()
{
delete[] m_pData;
}
#ifdef _DEBUG
int CBinaryHeap::Enqueue(intiVal) //入队列
{
if(m_iAmount==m_iSize)
return 0;
//Put this value to the end of the array.
m_pData[m_iAmount]= iVal; //值放到数组最后,即二叉堆的最后
++m_iAmount;
int iIndex = m_iAmount - 1;
while(m_pData[iIndex] < m_pData[(iIndex-1)/2]) //循环和上一层比较,如果小于上一层则交换位置。
{
//Swap the two value
SWAP_TWO_INT(m_pData[iIndex],m_pData[(iIndex-1)/2])
iIndex= (iIndex-1)/2;//完全二叉树在数组中的位置固定
}
return 1;
}
#endif
int CBinaryHeap::Dequeue(int&iVal)//出队列
{
if(m_iAmount==0)
return 0;
iVal= m_pData[0]; //返回根节点
int iIndex = 0;
while (iIndex*2 < m_iAmount)
{
int iLeft = (iIndex*2+1 <m_iAmount)?(iIndex*2+1):0;
int iRight = (iIndex*2+2 <m_iAmount)?(iIndex*2+2):0;
if(iLeft && iRight) //Both left and right exists. 将根节点的子节点中较小的元素和根节点交换。
{
if(m_pData[iLeft]<m_pData[iRight])
{
SWAP_TWO_INT(m_pData[iIndex],m_pData[iLeft])
iIndex= iLeft;
}
else
{
SWAP_TWO_INT(m_pData[iIndex],m_pData[iRight])
iIndex= iRight;
}
}
else if(iLeft) //The iRight must be 0
{
SWAP_TWO_INT(m_pData[iIndex],m_pData[iLeft])
iIndex= iLeft;
break;
}
else
{
break;
}
}
//Move the last element to the blank position.
//Of course, if it is the blank one, forget it.
if(iIndex!=m_iAmount-1)
{
m_pData[iIndex]= m_pData[m_iAmount-1]; //将最后一个元素移到目前根节点所在的位置
//Try to move this element to the top as high as possible.
while(m_pData[iIndex] < m_pData[(iIndex-1)/2])
{
//Swap the two value
SWAP_TWO_INT(m_pData[iIndex],m_pData[(iIndex-1)/2])
iIndex= (iIndex-1)/2;
}
}
--m_iAmount;
return 1;
}
int CBinaryHeap::GetMin(int&iVal)
{
if(m_iAmount==0)
return 0;
iVal= m_pData[0];
return 1;
}
void CBinaryHeap::PrintQueue()
{
int i;
for(i=0; i<m_iAmount; i++)
{
printf("%d ", m_pData[i]);
}
printf("\n");
}
int main(int argc, char* argv[])
{
CBinaryHeapbh;
bh.Enqueue(4); //入队
bh.Enqueue(1);
bh.Enqueue(3);
bh.Enqueue(2);
bh.Enqueue(6);
bh.Enqueue(5);
#ifdef _DEBUG
bh.PrintQueue();
#endif
int iVal;
bh.Dequeue(iVal);//出队
bh.Dequeue(iVal);
#ifdef _DEBUG
bh.PrintQueue();
#endif
return 0;
}
7 排序算法
7.1 //冒泡排序
void BubblerSort(int *pArray,int iElementNum)
{
int i, j, x;
for(i=0; i<iElementNum-1; i++) //
{
for(j=0; j<iElementNum-1-i; j++)
{
if(pArray[j]>pArray[j+1])
{
x= pArray[j];
&nbs p; pArray[j]= pArray[j+1];
pArray[j+1]= x;
}
}
}
}
//内部每一次循环将数组中最大的元素移到最后
//外部循环n-1次排序完毕,
//复杂度n*n/2 = n*n
7.2 //直接插入排序
//第一次取数组前个排序,第二次将第三个元素插入前面已经排好序的个数里面
//第三次将第个元素插入到前面个已排好序的元素里面
void StraightInsertionSort(int*pArray, int iElementNum)
{
int i, j, k;
for(i=0; i<iElementNum; i++)
{
int iHandling = pArray[i];
for(j=i; j>0; j--) //循环比较查找要插入的位置。。
{
if(iHandling>=pArray[j-1]) //找到要插入的位置
break;
}
for(k=i; k>j; k--) //将要插入的元素插入到指定位置,后面的元素依次顺移
pArray[k]= pArray[k-1];
pArray[j]= iHandling;
}
}
7.3 //二分插入排序
//和直接插入排序基本一样,只是在插入元素的时候利用了二分查找
void BinaryInsertionSort(int*pArray, int iElementNum)
{
int i, j, k;
for(i=0; i<iElementNum; i++)
{
int iHandling = pArray[i];
int iLeft = 0;
int iRight = i-1;
while(iLeft<=iRight) //二分查找要插入的位置
{
int iMiddle = (iLeft+iRight)/2;
if(iHandling < pArray[iMiddle])
{
iRight= iMiddle-1;
}
else if(iHandling> pArray[iMiddle])
{
iLeft= iMiddle+1;
}
else
{
j= iMiddle + 1;
break;
}
}
if(iLeft>iRight)
j= iLeft; //如果没有找到,即不需要移动位置了。
for(k=i; k>j; k--)
pArray[k]= pArray[k-1];
pArray[j]= iHandling;
}
}
//直接选择排序
//每循环一次把最大的元素取出来和最后一个元素交换
void StraightSelectionSort(int*pArray, int iElementNum)
{
int iEndIndex, i, iMaxIndex, x;
for(iEndIndex=iElementNum-1; iEndIndex>0;iEndIndex--)
{
for(i=0, iMaxIndex=0; i<iEndIndex; i++) //找出最大的元素
{
if(pArray[i]>=pArray[iMaxIndex])
iMaxIndex= i;
}
x= pArray[iMaxIndex]; //和最后一个元素交互
pArray[iMaxIndex]= pArray[iEndIndex];
pArray[iEndIndex]= x;
}
}
//快速排序,利用递归。
void QuickSort(int *pArray, int iElementNum)
{
int iTmp;
//Select the pivot make it to the right side.
int& iLeftIdx = pArray[0];
int& iRightIdx = pArray[iElementNum-1];
int& iMiddleIdx = pArray[(iElementNum-1)/2];
if(iLeftIdx>iMiddleIdx)
{
iTmp= iLeftIdx;
iLeftIdx= iMiddleIdx;
iMiddleIdx= iTmp;
}
if(iRightIdx>iMiddleIdx)
{
iTmp= iRightIdx;
iRightIdx= iMiddleIdx;
iMiddleIdx= iTmp;
}
if(iLeftIdx>iRightIdx)
{
iTmp= iLeftIdx;
iLeftIdx= iRightIdx;
iRightIdx= iTmp;
} //1:将左中右个元素的处于中间大小的元素放到数组的最后面,设为iPivot,为最开始的基础比较数据。
//2: 从数组第一个开始往后找到第一个大于iPivot的值,设为iLeft;从数组倒数第二个开始往前找到第一个小于iPivot的值iRight。然后交换iLeft和iRight。
//Make pivot's left element and right element.
int iLeft = 0;
int iRight = iElementNum-2;
int& iPivot = pArray[iElementNum-1];
while (1)
{
while (iLeft<iRight &&pArray[iLeft]<iPivot) ++iLeft;
while (iLeft<iRight &&pArray[iRight]>=iPivot) --iRight;
if(iLeft>=iRight)
break;
iTmp= pArray[iLeft];
pArray[iLeft]= pArray[iRight];
pArray[iRight]= iTmp; //交换iLeft和iRight
}
//Make the i
if(pArray[iLeft]>iPivot) //这次交换有2个目的 1 小的在前面 2使比较的元素更接近平均值
{
iTmp= pArray[iLeft];
pArray[iLeft]= iPivot;
iPivot= iTmp;
}
if(iLeft>1)
QuickSort(pArray,iLeft); //对前半部分排序
if(iElementNum-iLeft-1>=1)
QuickSort(&pArray[iLeft+1],iElementNum-iLeft-1); //对后半部分排序
}
//桶排序,例如buckets[100]=10 表示pArray中数值为100的元素有10个
void BucketSort(int *pArray, int iElementNum)
{
int buckets[RAND_MAX]; // RAND_MAX 这个值需要囊括所有的pArray中的元素,不好把握。
memset(buckets, 0, sizeof(buckets));
int i;
for(i=0; i<iElementNum; i++)
{
++buckets[pArray[i]-1];
}
int iAdded = 0;
for(i=0; i<RAND_MAX; i++)
{
while((buckets[i]--)>0)
{
pArray[iAdded++] = i;
}
}
}